From ce28e6f32f8eb723c16777e62b35645586ab6e37 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 17 Mar 2025 22:59:19 -0400 Subject: [PATCH 01/41] Add a release note that should have been in !3073. --- doc/release/notes/geometry-attribute.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 doc/release/notes/geometry-attribute.rst diff --git a/doc/release/notes/geometry-attribute.rst b/doc/release/notes/geometry-attribute.rst new file mode 100644 index 0000000000..ff7f1c06bb --- /dev/null +++ b/doc/release/notes/geometry-attribute.rst @@ -0,0 +1,12 @@ +Geometry Subsystem +================== + +Attribute Resources with Renderable Geometry +-------------------------------------------- + +The ``smtk::extension::vtk::source::SourceFromAttribute`` class has been removed. +This class provided no renderable geometry for attribute resources +and interfered with other plugins that do provide renderable geometry. + +Previously, it served to force creation of a ParaView pipeline object +for attribute resources, but the need for this no longer exists. -- GitLab From 606c6737bcefc4b3e2e4adb611e919daccb39689 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 23 Feb 2025 00:41:29 -0500 Subject: [PATCH 02/41] Fix a typo. --- smtk/view/Configuration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smtk/view/Configuration.h b/smtk/view/Configuration.h index 3eaac22f72..fa9b94370a 100644 --- a/smtk/view/Configuration.h +++ b/smtk/view/Configuration.h @@ -63,7 +63,7 @@ public: /// is t or true, false if attribute is f or false and not set otherwise /// set value to the attribute's values. Else it returns false bool attributeAsBool(const std::string& attname, bool& value) const; - /// Returns true if the component has an attribute called name and if it's value is + /// Returns true if the component has an attribute called name and if its value is /// either t or true (ignoring case). Else it returns false. bool attributeAsBool(const std::string& attname) const; -- GitLab From 118c7b160adf76d1611c8d874f2dfd27a19bb381 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 23 Feb 2025 01:37:42 -0500 Subject: [PATCH 03/41] Add a view for task controls. This view can be placed in the attribute panel or toolbox parameter panel to allow users to see instructions, diagnostics, and control task completion without switching to the diagram panel. --- .../appcomponents/pqSMTKDiagramPanel.cxx | 18 + .../appcomponents/pqSMTKDiagramPanel.h | 4 + .../extension/paraview/project/CMakeLists.txt | 1 + smtk/extension/paraview/project/Registrar.cxx | 9 +- .../project/pqSMTKTaskResourceVisibility.cxx | 2 +- .../paraview/project/pqTaskControlView.cxx | 355 ++++++++++++++++++ .../paraview/project/pqTaskControlView.h | 68 ++++ smtk/extension/qt/diagram/qtBaseTaskNode.cxx | 4 +- 8 files changed, 456 insertions(+), 5 deletions(-) create mode 100644 smtk/extension/paraview/project/pqTaskControlView.cxx create mode 100644 smtk/extension/paraview/project/pqTaskControlView.h diff --git a/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.cxx b/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.cxx index 12d042d3ae..857f359ed1 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.cxx @@ -211,3 +211,21 @@ bool pqSMTKDiagramPanel::configure(const nlohmann::json& data) } return false; } + +void pqSMTKDiagramPanel::focusPanel() +{ + // If we are owned by a dock widget, ensure the dock is shown. + if (auto* dock = qobject_cast(this->parent())) + { + auto* action = dock->toggleViewAction(); + if (!action->isChecked()) + { + action->trigger(); + } + } + // Raise our parent widget. + if (auto* parent = qobject_cast(this->parent())) + { + parent->raise(); + } +} diff --git a/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h b/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h index 3f3f9115cd..6c3e0759a6 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h +++ b/smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h @@ -63,6 +63,10 @@ public: Q_SIGNALS: void titleChanged(QString title); +public Q_SLOTS: + /// Bring this panel into focus (showing and raising it as required). + virtual void focusPanel(); + protected Q_SLOTS: virtual void resourceManagerAdded(pqSMTKWrapper* mgr, pqServer* server); virtual void resourceManagerRemoved(pqSMTKWrapper* mgr, pqServer* server); diff --git a/smtk/extension/paraview/project/CMakeLists.txt b/smtk/extension/paraview/project/CMakeLists.txt index 3da2593762..b13b2cc52f 100644 --- a/smtk/extension/paraview/project/CMakeLists.txt +++ b/smtk/extension/paraview/project/CMakeLists.txt @@ -4,6 +4,7 @@ set(classes pqSMTKProjectMenu pqSMTKProjectPanel pqSMTKTaskResourceVisibility + pqTaskControlView Registrar ) diff --git a/smtk/extension/paraview/project/Registrar.cxx b/smtk/extension/paraview/project/Registrar.cxx index f8e72dfca4..bf0d584921 100644 --- a/smtk/extension/paraview/project/Registrar.cxx +++ b/smtk/extension/paraview/project/Registrar.cxx @@ -12,6 +12,7 @@ #include "smtk/extension/paraview/project/Registrar.h" #include "smtk/extension/paraview/project/pqSMTKProjectBrowser.h" +#include "smtk/extension/paraview/project/pqTaskControlView.h" namespace smtk { @@ -21,6 +22,9 @@ namespace paraview { namespace project { + +using ViewWidgetList = std::tuple; + void Registrar::registerTo(const smtk::project::Manager::Ptr& projectManager) { projectManager->registerProject("basic"); @@ -34,13 +38,14 @@ void Registrar::unregisterFrom(const smtk::project::Manager::Ptr& projectManager void Registrar::registerTo(const smtk::view::Manager::Ptr& viewManager) { (void)viewManager; - viewManager->viewWidgetFactory().registerType(); + viewManager->viewWidgetFactory().registerTypes(); + viewManager->viewWidgetFactory().addAlias("TaskControl"); } void Registrar::unregisterFrom(const smtk::view::Manager::Ptr& viewManager) { (void)viewManager; - viewManager->viewWidgetFactory().unregisterType(); + viewManager->viewWidgetFactory().unregisterTypes(); } } // namespace project } // namespace paraview diff --git a/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx b/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx index 8e334c90ed..0f89fe8539 100644 --- a/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx +++ b/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx @@ -318,7 +318,7 @@ void pqSMTKTaskResourceVisibility::handleTaskEvent( auto* pqCore = pqApplicationCore::instance(); if (pqCore) { - if (auto* panel = dynamic_cast(pqCore->manager("smtk task diagram"))) + if (auto* panel = dynamic_cast(pqCore->manager("smtk task panel"))) { for (const auto& generatorEntry : panel->diagram()->generators()) { diff --git a/smtk/extension/paraview/project/pqTaskControlView.cxx b/smtk/extension/paraview/project/pqTaskControlView.cxx new file mode 100644 index 0000000000..06c44c47da --- /dev/null +++ b/smtk/extension/paraview/project/pqTaskControlView.cxx @@ -0,0 +1,355 @@ +//========================================================================= +// 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/extension/paraview/project/pqTaskControlView.h" + +#include "smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h" + +#include "smtk/extension/qt/qtUIManager.h" + +#include "smtk/view/Configuration.h" +#include "smtk/view/Manager.h" + +#include "smtk/project/Manager.h" +#include "smtk/task/Active.h" +#include "smtk/task/Manager.h" + +#include "smtk/common/Managers.h" + +#include "smtk/io/Logger.h" + +#include "pqApplicationCore.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace smtk::string::literals; + +namespace smtk +{ +namespace extension +{ + +/// Private storage for a task-control views. +class pqTaskControlView::Internal +{ +public: + Internal(pqTaskControlView* self) + : m_view(self) + { + } + + /// Add a widget for a \a childSpec. + /// + /// If \a layout is non-null and appropriate for the \a childSpec, + /// the current \a layout and \a widget are used. Otherwise, a new + /// layout is created. + bool addChildItem( + const smtk::view::Configuration::Component& childSpec, + QLayout*& layout, + QWidget*& parent, + int& childNum, + smtk::task::Task* task); + + /// Called when the active task changes. + void activeTaskChange(smtk::task::Task* prev, smtk::task::Task* next) + { + (void)prev; + (void)next; + m_view->updateWithActiveTask(next); + // m_view->buildUI(); + } + + pqTaskControlView* m_view{ nullptr }; + smtk::project::Observers::Key m_projectObserverKey; + smtk::task::Active::Observers::Key m_activeTaskObserverKey; +}; + +bool pqTaskControlView::Internal::addChildItem( + const smtk::view::Configuration::Component& childSpec, + QLayout*& layout, + QWidget*& parent, + int& childNum, + smtk::task::Task* task) +{ + bool needLayout = !layout; + smtk::string::Token childType = childSpec.name(); + static std::unordered_set formItems{ "ActiveTaskStatus"_token, + "ObjectControl"_token }; + + bool isFormItem = (formItems.find(childType) != formItems.end()); + auto* formLayout = dynamic_cast(layout); + needLayout |= (isFormItem && !formLayout) || (!isFormItem && formLayout); + if (needLayout) + { + parent = new QWidget(); + std::ostringstream pname; + pname << "TaskControlWidget" << childNum++; + parent->setObjectName(pname.str().c_str()); + if (isFormItem) + { + formLayout = new QFormLayout(parent); + layout = formLayout; + } + else + { + layout = new QVBoxLayout(parent); + } + m_view->widget()->layout()->addWidget(parent); + } + switch (childType.id()) + { + case "ActiveTaskStatus"_hash: + { + auto* label = new QLabel(); + label->setObjectName("taskName"); + auto txt = QString::fromStdString(task ? task->name() : ""); + if (childSpec.attributeAsBool("ReturnToDiagram")) + { + txt += QString::fromStdString(""); + } + label->setText(txt); + label->setOpenExternalLinks(false); + QObject::connect(label, &QLabel::linkActivated, m_view, &pqTaskControlView::returnToDiagram); + auto* ctrl = new QPushButton(); + ctrl->setObjectName("taskCompleter"); + ctrl->setText(task ? (task->isCompleted() ? "Completed" : "Complete") : "Complete"); + ctrl->setCheckable(true); + ctrl->setChecked(task ? task->isCompleted() : false); + ctrl->setEnabled(task ? task->state() >= smtk::task::State::Completable : false); + QObject::connect( + ctrl, &QAbstractButton::toggled, m_view, &pqTaskControlView::updateTaskCompletion); + formLayout->addRow(label, ctrl); + } + break; + case "ActiveTaskDescription"_hash: + { + auto* description = new QLabel(); + smtk::task::Task::InformationOptions opt; + description->setObjectName("taskDescription"); + description->setText(QString::fromStdString(task ? task->information(opt) : "")); + description->setWordWrap(true); // Allow line breaks so panel is not forced to be crazy wide. + layout->addWidget(description); + } + break; + case "ObjectControl"_hash: + break; + case "OperationControl"_hash: + break; + default: + return false; + break; + } + return true; +} + +qtBaseView* pqTaskControlView::createViewWidget(const smtk::view::Information& info) +{ + if (!qtBaseView::validateInformation(info)) + { + return nullptr; // \a info is not suitable for this View + } + auto* view = new pqTaskControlView(info); + view->buildUI(); + return view; +} + +pqTaskControlView::pqTaskControlView(const smtk::view::Information& info) + : qtBaseView(info) +{ + m_p = new Internal(this); + // auto viewConfig = this->configuration(); + // if (viewConfig) + // { + // } +} + +pqTaskControlView::~pqTaskControlView() +{ + delete m_p; +} + +bool pqTaskControlView::isEmpty() const +{ + // TODO + return false; +} + +bool pqTaskControlView::isValid() const +{ + // This view is always valid for now. + return true; +} + +void pqTaskControlView::updateUI() +{ + auto projMgr = this->uiManager()->managers().get(); + auto project = projMgr ? *projMgr->projects().begin() : nullptr; + auto* taskMgr = project ? &project->taskManager() : nullptr; + auto* activeTask = taskMgr ? taskMgr->active().task() : nullptr; + this->updateWithActiveTask(activeTask); +} + +void pqTaskControlView::showAdvanceLevelOverlay(bool show) +{ + this->qtBaseView::showAdvanceLevelOverlay(show); +} + +void pqTaskControlView::onShowCategory() +{ + this->Superclass::onShowCategory(); +} + +void pqTaskControlView::returnToDiagram() +{ + auto* pqCore = pqApplicationCore::instance(); + if (!pqCore) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Cannot return to diagram panel as there is no application core."); + return; + } + auto* panel = dynamic_cast(pqCore->manager("smtk task panel")); + if (!panel) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "Cannot return to diagram panel as there is no diagram panel."); + return; + } + panel->focusPanel(); +} + +void pqTaskControlView::updateTaskCompletion(bool completed) +{ + auto projMgr = this->uiManager()->managers().get(); + auto project = projMgr ? *projMgr->projects().begin() : nullptr; + auto* taskMgr = project ? &project->taskManager() : nullptr; + if (taskMgr) + { + auto* task = taskMgr->active().task(); + if (task && task->state() >= smtk::task::State::Completable) + { + QTimer::singleShot(0, [this, task, completed]() { + if (task->markCompleted(completed) && completed) + { + this->returnToDiagram(); + } + }); + } + } +} + +void pqTaskControlView::buildUI() +{ + this->createWidget(); + + QVBoxLayout* parentlayout = static_cast(this->parentWidget()->layout()); + if (!parentlayout) + { + smtkErrorMacro(smtk::io::Logger::instance(), "No parent layout for task control view."); + return; + } + + if (!this->isTopLevel()) + { + parentlayout->setAlignment(Qt::AlignTop); + parentlayout->addWidget(this->Widget); + return; + } + // XXX TODO. +} + +void pqTaskControlView::createWidget() +{ + smtk::view::ConfigurationPtr view = this->configuration(); + if (!view) + { + return; + } + // If we have a pre-existing widget, destroy it as we are about to recreate it. + QVBoxLayout* parentlayout = static_cast(this->parentWidget()->layout()); + if (this->Widget) + { + if (parentlayout) + { + parentlayout->removeWidget(this->Widget); + } + delete this->Widget; + } + + this->Widget = new QFrame(this->parentWidget()); + this->Widget->setObjectName(view->name().c_str()); + + // Create the layout for the frame + QVBoxLayout* layout = new QVBoxLayout(this->Widget); + layout->setMargin(0); + this->Widget->setLayout(layout); + + // Start observing changes in the active task: + auto projMgr = this->uiManager()->managers().get(); + auto project = projMgr ? *projMgr->projects().begin() : nullptr; + auto* taskMgr = project ? &project->taskManager() : nullptr; + if (taskMgr) + { + m_p->m_activeTaskObserverKey = taskMgr->active().observers().insert( + [&](smtk::task::Task* prev, smtk::task::Task* next) { m_p->activeTaskChange(prev, next); }, + 0, + false, + "TaskControlView"); + } + else + { + m_p->m_activeTaskObserverKey.release(); + } + + this->updateUI(); +} + +void pqTaskControlView::updateWithActiveTask(smtk::task::Task* task) +{ + smtk::view::ConfigurationPtr view = this->configuration(); + if (!view) + { + return; + } + // Remove all previous children (if any). + if (this->widget()) + { + for (auto* child : + this->widget()->findChildren(QString(), Qt::FindDirectChildrenOnly)) + { + delete child; + } + } + + // Now process all of this view's entries + QLayout* activeLayout = nullptr; + QWidget* activeWidget = nullptr; + int childNum = 0; + for (const auto& child : view->details().children()) + { + m_p->addChildItem(child, activeLayout, activeWidget, childNum, task); + } +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/paraview/project/pqTaskControlView.h b/smtk/extension/paraview/project/pqTaskControlView.h new file mode 100644 index 0000000000..d125299b36 --- /dev/null +++ b/smtk/extension/paraview/project/pqTaskControlView.h @@ -0,0 +1,68 @@ +//========================================================================= +// 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_extension_pqTaskControlView_h +#define smtk_extension_pqTaskControlView_h + +#include "smtk/extension/paraview/project/smtkPQProjectExtModule.h" +#include "smtk/extension/qt/qtBaseView.h" + +namespace smtk +{ +namespace extension +{ + +/**\brief A view for task-level controls. + * + * Views of this type may contain any number of widgets for manipulating + * the currently-active task's completion status, port data, etc. + * The view may also be configured to show diagnostic/summary information + * about the active task. + */ +class SMTKPQPROJECTEXT_EXPORT pqTaskControlView : public qtBaseView +{ + Q_OBJECT + +public: + smtkTypenameMacro(smtk::extension::pqTaskControlView); + smtkSuperclassMacro(smtk::extension::qtBaseView); + + static qtBaseView* createViewWidget(const smtk::view::Information& info); + pqTaskControlView(const smtk::view::Information& info); + ~pqTaskControlView() override; + + /// Returns true if the view does not contain any information to display. + bool isEmpty() const override; + + /// Returns false when users must use the view to adjust the workflow. + /// + /// Currently this always returns true, even when the active task is incomplete. + bool isValid() const override; + +public Q_SLOTS: + void updateUI() override; + void showAdvanceLevelOverlay(bool show) override; + void onShowCategory() override; + void returnToDiagram(); + void updateTaskCompletion(bool completed); + +protected: + void buildUI() override; + void createWidget() override; + void updateWithActiveTask(smtk::task::Task* task); + +private: + class Internal; + Internal* m_p; +}; + +} // namespace extension +} // namespace smtk + +#endif diff --git a/smtk/extension/qt/diagram/qtBaseTaskNode.cxx b/smtk/extension/qt/diagram/qtBaseTaskNode.cxx index 22a79b7510..7fdbb840f8 100644 --- a/smtk/extension/qt/diagram/qtBaseTaskNode.cxx +++ b/smtk/extension/qt/diagram/qtBaseTaskNode.cxx @@ -91,7 +91,7 @@ void qtBaseTaskNode::updateTaskState(smtk::task::State prev, smtk::task::State n (void)active; // Update the tool tip with diagnostic information smtk::task::Task::InformationOptions opt; - opt.m_includeTitle = false; + opt.m_includeTitle = true; this->setToolTip(QString::fromStdString(m_task->information(opt))); } @@ -101,7 +101,7 @@ void qtBaseTaskNode::dataUpdated() // Update the tool tip with diagnostic information smtk::task::Task::InformationOptions opt; - opt.m_includeTitle = false; + opt.m_includeTitle = true; this->setToolTip(QString::fromStdString(m_task->information(opt))); } -- GitLab From 635bf4a78df1b3b22bdb636162923002b49a697a Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 13 Mar 2025 15:52:20 -0400 Subject: [PATCH 04/41] Refactor visibility behavior; add task-view controls. + This moves task-port code (that collects subsets of port data) to task::Manager. + Adds a TaskControl view to SMTK in the smtkPQProjectExt library (via the smtk::extension::paraview::project::Registrar). + Adds utility code to convert objects group by their parent resource into objects grouped by their parent ParaView representation. + Adds code to the task manager to convert workflow "specification" data in the XML-like smtk::view::Configuration::Component to a JSON form. This allows the same code to be used by both JSON and XML configuration data. Typically views are configured with XML while project workflows (tasks and their styles) are configured by JSON. Instead of insisting the JSON have a confusing 1-to-1 correspondence with the way smtk::view::Configuration::Component serializes itself, this provides a more natural JSON specification. --- .../extension/paraview/project/CMakeLists.txt | 1 + smtk/extension/paraview/project/Utility.cxx | 71 ++++ smtk/extension/paraview/project/Utility.h | 62 +++ .../project/pqSMTKTaskResourceVisibility.cxx | 265 +------------ .../project/pqSMTKTaskResourceVisibility.h | 23 +- .../paraview/project/pqTaskControlView.cxx | 292 +++++++++++++-- .../paraview/project/pqTaskControlView.h | 5 +- smtk/task/Manager.cxx | 352 ++++++++++++++++++ smtk/task/Manager.h | 38 ++ 9 files changed, 800 insertions(+), 309 deletions(-) create mode 100644 smtk/extension/paraview/project/Utility.cxx create mode 100644 smtk/extension/paraview/project/Utility.h diff --git a/smtk/extension/paraview/project/CMakeLists.txt b/smtk/extension/paraview/project/CMakeLists.txt index b13b2cc52f..bf00a7d8d5 100644 --- a/smtk/extension/paraview/project/CMakeLists.txt +++ b/smtk/extension/paraview/project/CMakeLists.txt @@ -6,6 +6,7 @@ set(classes pqSMTKTaskResourceVisibility pqTaskControlView Registrar + Utility ) smtk_encode_file("${CMAKE_CURRENT_SOURCE_DIR}/ProjectPanelConfiguration.json" diff --git a/smtk/extension/paraview/project/Utility.cxx b/smtk/extension/paraview/project/Utility.cxx new file mode 100644 index 0000000000..1f072b92b3 --- /dev/null +++ b/smtk/extension/paraview/project/Utility.cxx @@ -0,0 +1,71 @@ +//========================================================================= +// 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/extension/paraview/project/Utility.h" + +#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKResource.h" + +#include "smtk/task/Manager.h" + +#include "pqDataRepresentation.h" + +namespace smtk +{ +namespace paraview +{ + +RepresentationObjectMap representationsOfObjects( + const smtk::task::Manager::ResourceObjectMap& resourcesToObjects) +{ + // Filter representations by the \a spec. + RepresentationObjectMap representations; + + auto* behavior = pqSMTKBehavior::instance(); + for (const auto& entry : resourcesToObjects) + { + smtk::resource::Resource* resource = entry.first; + if (!resource) + { + continue; + } + + QPointer pvrsrc = behavior->getPVResource(resource->shared_from_this()); + if (!pvrsrc) + { + continue; + } + + for (const auto& view : pvrsrc->getViews()) + { + for (const auto& representation : pvrsrc->getRepresentations(view)) + { + for (const auto& object : entry.second) + { + representations[representation].insert(object); + } + } + } + } + + return representations; +} + +RepresentationObjectMap relevantRepresentations( + const nlohmann::json& spec, + smtk::task::Manager* taskMgr, + smtk::task::Task* task) +{ + auto objectMap = taskMgr->workflowObjects(spec, task); + auto representations = representationsOfObjects(objectMap); + return representations; +} + +} // namespace paraview +} // namespace smtk diff --git a/smtk/extension/paraview/project/Utility.h b/smtk/extension/paraview/project/Utility.h new file mode 100644 index 0000000000..450c97f0c4 --- /dev/null +++ b/smtk/extension/paraview/project/Utility.h @@ -0,0 +1,62 @@ +//========================================================================= +// 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_extension_paraview_project_Utility_h +#define smtk_extension_paraview_project_Utility_h +/*!\file Utility.h + * Functions provided here are for working with projects and tasks + * in ParaView's user interface. + * + * The methods here can be used to fetch ParaView pipeline objects and the + * blocks within them that are relevant to a particular SMTK component or + * resource. + */ + +#include "smtk/extension/paraview/project/smtkPQProjectExtModule.h" + +#include "smtk/project/Observer.h" +#include "smtk/task/Active.h" +#include "smtk/task/Manager.h" +#include "smtk/task/Task.h" + +#include "smtk/PublicPointerDefs.h" + +class pqDataRepresentation; + +namespace smtk +{ +namespace paraview +{ + +using RepresentationObjectMap = + std::map>; + +/// Given a map from resources to persistent objects, return a map from +/// representations tied to the resource keys to persistent object values. +/// +/// This is intended for use by user interface code that needs to adjust the visibility of +/// components and/or resources. +RepresentationObjectMap representationsOfObjects( + const smtk::task::Manager::ResourceObjectMap& resourcesToObjects); + +/// Return a list of representations that are relevant to \a spec and \a task. +/// +/// These representations will correspond to a resources that match +/// a directive above because (a) they are part of the project owning the +/// task manager or (b) they are on input ports of the task referenced by +/// the \a spec. +RepresentationObjectMap relevantRepresentations( + const nlohmann::json& spec, + smtk::task::Manager* taskMgr, + smtk::task::Task* task); + +} // namespace paraview +} // namespace smtk + +#endif // smtk_extension_paraview_project_Utility_h diff --git a/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx b/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx index 0f89fe8539..5bc77e8ce7 100644 --- a/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx +++ b/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.cxx @@ -13,6 +13,7 @@ #include "smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h" #include "smtk/extension/paraview/appcomponents/pqSMTKResource.h" #include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h" +#include "smtk/extension/paraview/project/Utility.h" #include "smtk/extension/qt/diagram/qtDiagram.h" #include "smtk/extension/qt/diagram/qtTaskEditor.h" #include "smtk/extension/qt/diagram/qtTaskPath.h" @@ -36,93 +37,16 @@ #include +// Change "#undef" to "#define" to print messages as this behavior +// responds to changes in tasks with 3-d view style. +#undef SMTK_DBG_3D_STYLE + using namespace smtk::string::literals; namespace { -pqSMTKTaskResourceVisibility* gInstance = nullptr; - -bool resourceMatch( - const std::string& filter, - smtk::resource::PersistentObject* object, - smtk::resource::Resource*& rsrc) -{ - if (auto* resource = dynamic_cast(object)) - { - rsrc = resource; - if (filter == "*" || rsrc->matchesType(filter)) - { - return true; - } - } - else if (auto* comp = dynamic_cast(object)) - { - if ((rsrc = comp->parentResource())) - { - if (filter == "*" || rsrc->matchesType(filter)) - { - return true; - } - } - } - return false; -} -bool componentMatch( - const std::string& filter, - smtk::resource::PersistentObject* object, - smtk::resource::Resource* rsrc) -{ - // An empty filter string indicates only resources are allowed. - if (filter.empty()) - { - return (object == rsrc); - } - // "*" allows any component: - else if (filter == "*") - { - // Assume that if object is not a pointer to the parent resource, - // it must be a component: - return (object != rsrc); - } - // We have a non-trivial filter string; we must have a component. - if (auto* comp = dynamic_cast(object)) - { - auto query = rsrc->queryOperation(filter); - return query ? query(*comp) : false; - } - return false; -} - -bool filterObject(const nlohmann::json::array_t& filter, smtk::resource::PersistentObject* object) -{ - for (const auto& filterTuple : filter) - { - try - { - if (filterTuple.is_array() && filterTuple.size() == 2) - { - smtk::resource::Resource* rsrc{ nullptr }; - if (resourceMatch(filterTuple[0].get(), object, rsrc)) - { - auto compSpec = filterTuple[1].is_null() ? "" : filterTuple[1].get(); - if (componentMatch(compSpec, object, rsrc)) - { - return true; - } - } - } - } - catch (nlohmann::json::exception& e) - { - smtkErrorMacro( - smtk::io::Logger::instance(), - "Cannot process filter \"" << filterTuple.dump() << "\";" << e.what()); - continue; - } - } - return false; -} +pqSMTKTaskResourceVisibility* gInstance = nullptr; } // anonymous namespace @@ -301,7 +225,6 @@ void pqSMTKTaskResourceVisibility::handleTaskEvent( { m_currentTask->observers().erase(m_currentTaskObserver); this->processTaskEvent(m_currentTask, "deactivated"_token); - std::cout << "Process \"deactivated\" event for \"" << m_currentTask->name() << "\".\n"; // self->displayResource(nullptr); } m_currentTask = nextTask; @@ -333,8 +256,10 @@ void pqSMTKTaskResourceVisibility::handleTaskEvent( } } } +#ifdef SMTK_DBG_3D_STYLE std::cout << "Process \"activated\" event for \"" << (m_currentTask ? m_currentTask->name() : "(default)") << "\".\n"; +#endif this->processTaskEvent(m_currentTask, "activated"_token); } @@ -398,7 +323,8 @@ void pqSMTKTaskResourceVisibility::applyColorBy( << spec.dump(2) << "\n"); return; } - auto representations = this->relevantRepresentations(spec); + auto representations = + smtk::paraview::relevantRepresentations(spec, m_currentTaskManager, m_currentTask); auto mode(spec.at("mode").get()); bool needsRender = false; for (const auto& entry : representations) @@ -463,7 +389,8 @@ void pqSMTKTaskResourceVisibility::applyShowObjects( << spec.dump(2) << "\n"); return; } - auto representations = this->relevantRepresentations(spec); + auto representations = + smtk::paraview::relevantRepresentations(spec, m_currentTaskManager, m_currentTask); for (const auto& entry : representations) { auto* representation = entry.first; @@ -481,171 +408,3 @@ void pqSMTKTaskResourceVisibility::applyShowObjects( pqActiveObjects::instance().activeView()->render(); } } - -std::map> -pqSMTKTaskResourceVisibility::relevantRepresentations(const nlohmann::json& spec) -{ - // Filter representations by the \a spec. - std::map> - representations; - - nlohmann::json source; - nlohmann::json filter; - if (spec.contains("source")) - { - source = spec.at("source"); - if (!source.is_object() || !source.contains("type")) - { - smtkErrorMacro( - smtk::io::Logger::instance(), - "Spec source must be a dictionary with 'type' key, " - "got \"" - << source.dump() << "\""); - return representations; - } - } - else - { - source = { { "type", "project resources" } }; - } - - if (spec.contains("filter")) - { - filter = spec.at("filter"); - if (!filter.is_array()) - { - smtkErrorMacro( - smtk::io::Logger::instance(), - "Spec filter must be an array, got \"" << filter.dump() << "\"."); - return representations; - } - } - else - { - // Accept any resource or component: - filter = nlohmann::json::array_t({ { "*", "*" }, { "*", nullptr } }); - } - - auto* behavior = pqSMTKBehavior::instance(); - auto sourceType = source["type"].get(); - std::unordered_set objects; - switch (sourceType.id()) - { - default: - smtkErrorMacro( - smtk::io::Logger::instance(), "Unknown source type \"" << sourceType.data() << "\"."); - // fall through - case "project resources"_hash: - { - if (!m_currentTaskManager) - { - return representations; - } - - auto* project = dynamic_cast(m_currentTaskManager->resource()); - if (!project) - { - return representations; - } - - for (const auto& resource : project->resources()) - { - if (filterObject(filter, resource.get())) - { - objects.insert(resource.get()); - } - } - } - break; - case "active task port"_hash: - { - if (!m_currentTask || !source.contains("port")) - { - smtkErrorMacro(smtk::io::Logger::instance(), "No active task or no \"port\" specified."); - return representations; - } - - // We are asked to find objects on a port of the (now) active task. - // Determine whether we are filtering on role or not. - smtk::string::Token sourceRole; - if (source.contains("role")) - { - sourceRole = source.at("role").get(); - } - // Find the correct port: - const auto& taskPortMap = m_currentTask->ports(); - auto it = taskPortMap.find(source["port"]); - if (it == taskPortMap.end()) - { - return representations; - } - // Fetch data from the port. We only understand ObjectsInRoles at this point: - auto portData = it->second->parent()->portData(it->second); - if (portData) - { - if (auto objectsInRoles = std::dynamic_pointer_cast(portData)) - { - for (const auto& entry : objectsInRoles->data()) - { - if (sourceRole.valid() && entry.first != sourceRole) - { // Skip objects in unrequested roles. - continue; - } - // Filter objects as requested. - for (const auto& object : entry.second) - { - if (filterObject(filter, object)) - { - objects.insert(object); - } - } - } - } - else - { - smtkErrorMacro( - smtk::io::Logger::instance(), - "Unhandled port data type \"" << portData->typeName() << "\"."); - } - } - } - break; - } - - for (const auto& object : objects) - { - smtk::resource::Resource* resource{ nullptr }; - QPointer pvrsrc; - if ((resource = dynamic_cast(object))) - { - pvrsrc = behavior->getPVResource(resource->shared_from_this()); - } - else if (auto* comp = dynamic_cast(object)) - { - resource = comp->resource().get(); - pvrsrc = behavior->getPVResource(comp->resource()); - } - if (!pvrsrc || !this->isResourceRelevant(resource->shared_from_this(), filter)) - { - continue; - } - - for (const auto& view : pvrsrc->getViews()) - { - for (const auto& representation : pvrsrc->getRepresentations(view)) - { - representations[representation].insert(object); - } - } - } - - return representations; -} - -bool pqSMTKTaskResourceVisibility::isResourceRelevant( - const std::shared_ptr& resource, - const nlohmann::json& filter) -{ - smtk::resource::Resource* rsrc = resource.get(); - return filterObject(filter, rsrc); -} diff --git a/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.h b/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.h index 11ebb9456b..288d5c13ff 100644 --- a/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.h +++ b/smtk/extension/paraview/project/pqSMTKTaskResourceVisibility.h @@ -55,10 +55,10 @@ class pqServer; * + "port" (with the name of a port on the active task) if the type is "active task port". * + "roles" (with the name of a role for data on the port) if the type is "active task port". * - * The "filter" must be an dictionary holding: + * The "filter" must be an array holding: * - * + "resources", an optional array of accepted resource type-names or "*". If not present, "*" is assumed. - * + "components", an optional array of accepted component-filters or "*". If not present, only + * + dictionaries with a "resource" and "component" key or + * + arrays holding two strings (a resource and component filter, in that order). * * When a task is deactivated and no new task is activated at the same time, * (1) if the task-path is empty (i.e., the top-level tasks are showing), then the default @@ -102,7 +102,8 @@ class pqServer; * + hide markup resources present on the "input" port of the active task (without toggling * per-component visibility) when a task with the "example" style is deactivated; and * + show both resources and components (toggling as needed) on the active task's "output" port - * when any task with the "example" style is deactivated. + * when any task with the "example" style is deactivated. (This way, as long as the task is + * active, its input port data is visible; when deactivated, its output port data is visible.) */ class SMTKPQPROJECTEXT_EXPORT pqSMTKTaskResourceVisibility : public QObject { @@ -136,20 +137,6 @@ protected: // NOLINT(readability-redundant-access-specifiers) smtk::task::Task* task, smtk::string::Token event); - /// Return a list of representations that are relevant to \a spec. - /// - /// These representations will correspond to a resources that match - /// a directive above because (a) they are part of the project owning the - /// task manager or (b) they are on input ports of the task referenced by - /// the \a spec. - std::map> - relevantRepresentations(const nlohmann::json& spec); - - /// Is the given resource relevant to the \a spec? - bool isResourceRelevant( - const std::shared_ptr& resource, - const nlohmann::json& filter); - std::map m_projectManagerObservers; smtk::task::Task* m_currentTask{ nullptr }; smtk::task::Manager* m_currentTaskManager{ nullptr }; diff --git a/smtk/extension/paraview/project/pqTaskControlView.cxx b/smtk/extension/paraview/project/pqTaskControlView.cxx index 06c44c47da..ea4f2d1a18 100644 --- a/smtk/extension/paraview/project/pqTaskControlView.cxx +++ b/smtk/extension/paraview/project/pqTaskControlView.cxx @@ -11,6 +11,9 @@ #include "smtk/extension/paraview/project/pqTaskControlView.h" #include "smtk/extension/paraview/appcomponents/pqSMTKDiagramPanel.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKResource.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKResourceRepresentation.h" +#include "smtk/extension/paraview/project/Utility.h" #include "smtk/extension/qt/qtUIManager.h" @@ -26,6 +29,12 @@ #include "smtk/io/Logger.h" #include "pqApplicationCore.h" +#include "pqSMAdaptor.h" +#include "pqView.h" + +#include "vtkSMProperty.h" +#include "vtkSMPropertyHelper.h" +#include "vtkSMProxy.h" #include #include @@ -36,17 +45,39 @@ #include #include #include +#include #include #include #include #include +using namespace smtk::paraview; using namespace smtk::string::literals; namespace smtk { namespace extension { +namespace // anonymous +{ + +// Return true if \a entry references a resource (as opposed to a component). +// +// This is used by buttons in the view to determine whether to toggle visibility of +// the entire representation or just a block within the representation. +bool representationObjectMapContainsResource(const RepresentationObjectMap::value_type& entry) +{ + auto pvDRep = dynamic_cast(entry.first); + auto pvRsrc = pvDRep ? dynamic_cast(pvDRep->getInput()) : nullptr; + if (!pvRsrc) + { + return false; + } + auto it = entry.second.find(pvRsrc->getResource().get()); + return (it != entry.second.end() || entry.second.find(nullptr) != entry.second.end()); +} + +} // anonymous namespace /// Private storage for a task-control views. class pqTaskControlView::Internal @@ -75,12 +106,136 @@ public: (void)prev; (void)next; m_view->updateWithActiveTask(next); - // m_view->buildUI(); + } + + void addTaskDescription( + QLayout* layout, + smtk::task::Task* task, + const smtk::view::Configuration::Component& spec) + { + (void)spec; // Currently no options to look up. + auto* description = new QLabel(); + smtk::task::Task::InformationOptions opt; + description->setObjectName("taskDescription"); + description->setText(QString::fromStdString(task ? task->information(opt) : "")); + description->setWordWrap(true); // Allow line breaks so panel is not forced to be crazy wide. + layout->addWidget(description); + ++this->numChildren; + } + + void addTaskStatus( + QFormLayout* formLayout, + smtk::task::Task* task, + const smtk::view::Configuration::Component& spec) + { + auto* label = new QLabel(); + label->setObjectName("taskName"); + auto txt = QString::fromStdString(task ? task->name() : ""); + if (spec.attributeAsBool("ReturnToDiagram")) + { + txt += QString::fromStdString(""); + } + label->setText(txt); + label->setOpenExternalLinks(false); + QObject::connect(label, &QLabel::linkActivated, m_view, &pqTaskControlView::returnToDiagram); + auto* ctrl = new QPushButton(); + ctrl->setObjectName("taskCompleter"); + ctrl->setText(task ? (task->isCompleted() ? "Completed" : "Complete") : "Complete"); + ctrl->setCheckable(true); + ctrl->setChecked(task ? task->isCompleted() : false); + ctrl->setEnabled(task ? task->state() >= smtk::task::State::Completable : false); + QObject::connect( + ctrl, &QAbstractButton::toggled, m_view, &pqTaskControlView::updateTaskCompletion); + formLayout->addRow(label, ctrl); + ++this->numChildren; + } + + void addRepresentationControl( + QFormLayout* formLayout, + smtk::task::Task* task, + const smtk::view::Configuration::Component& spec) + { + (void)task; + auto* field = new QHBoxLayout; + std::string name; + if (!spec.attribute("Name", name)) + { + smtkWarningMacro(smtk::io::Logger::instance(), "RepresentationControl without name."); + name = "Unknown"; + } + std::string label; + if (!spec.attribute("Label", label)) + { + smtkWarningMacro(smtk::io::Logger::instance(), "RepresentationControl without label."); + label = name; + } + for (const auto& controlSpec : spec.children()) + { + if (controlSpec.name() == "Control") + { + if (controlSpec.attributeAsString("Type") == "Visibility") + { + auto* ctrl = new QPushButton; + ctrl->setObjectName("visibility"); + ctrl->setText("Visibility"); + ctrl->setCheckable(true); + ctrl->setToolTip("Toggle visibility"); + bool initiallyVisible{ true }; + controlSpec.attributeAsBool("InitiallyVisible", initiallyVisible); + ctrl->setChecked(initiallyVisible); + m_representationSpecs[name] = spec; + QObject::connect(ctrl, &QPushButton::toggled, [this, name](bool toggled) { + m_view->toggleVisibility(name, toggled); + }); + field->addWidget(ctrl); + // Now force state to match button upon view construction. + m_view->toggleVisibility(name, initiallyVisible); + } + else if (controlSpec.attributeAsString("Type") == "Opacity") + { + auto* ctrl = new QSlider; + ctrl->setObjectName("opacity"); + ctrl->setMinimum(0); + ctrl->setMaximum(255); + ctrl->setPageStep(16); + ctrl->setOrientation(Qt::Horizontal); + int initialValue{ 255 }; + controlSpec.attributeAsInt("InitialValue", initialValue); + ctrl->setValue(initialValue); + ctrl->setToolTip("Adjust opacity"); + m_representationSpecs[name] = spec; + QObject::connect(ctrl, &QSlider::valueChanged, [this, name](int value) { + m_view->updateOpacity(name, value / 255.0); + }); + field->addWidget(ctrl); + // Now force state to match slider upon view construction. + m_view->updateOpacity(name, initialValue / 255.); + } + } + } + formLayout->addRow(QString::fromStdString(label), field); + ++this->numChildren; + } + + void addOperationControl( + QLayout* layout, + smtk::task::Task* task, + const smtk::view::Configuration::Component& spec) + { + (void)layout; + (void)task; + (void)spec; + ++this->numChildren; } pqTaskControlView* m_view{ nullptr }; + smtk::task::Manager* m_currentTaskManager{ nullptr }; + smtk::task::Task* m_currentTask{ nullptr }; smtk::project::Observers::Key m_projectObserverKey; smtk::task::Active::Observers::Key m_activeTaskObserverKey; + std::unordered_map + m_representationSpecs; + int numChildren{ 0 }; }; bool pqTaskControlView::Internal::addChildItem( @@ -93,7 +248,7 @@ bool pqTaskControlView::Internal::addChildItem( bool needLayout = !layout; smtk::string::Token childType = childSpec.name(); static std::unordered_set formItems{ "ActiveTaskStatus"_token, - "ObjectControl"_token }; + "RepresentationControl"_token }; bool isFormItem = (formItems.find(childType) != formItems.end()); auto* formLayout = dynamic_cast(layout); @@ -117,42 +272,17 @@ bool pqTaskControlView::Internal::addChildItem( } switch (childType.id()) { - case "ActiveTaskStatus"_hash: - { - auto* label = new QLabel(); - label->setObjectName("taskName"); - auto txt = QString::fromStdString(task ? task->name() : ""); - if (childSpec.attributeAsBool("ReturnToDiagram")) - { - txt += QString::fromStdString(""); - } - label->setText(txt); - label->setOpenExternalLinks(false); - QObject::connect(label, &QLabel::linkActivated, m_view, &pqTaskControlView::returnToDiagram); - auto* ctrl = new QPushButton(); - ctrl->setObjectName("taskCompleter"); - ctrl->setText(task ? (task->isCompleted() ? "Completed" : "Complete") : "Complete"); - ctrl->setCheckable(true); - ctrl->setChecked(task ? task->isCompleted() : false); - ctrl->setEnabled(task ? task->state() >= smtk::task::State::Completable : false); - QObject::connect( - ctrl, &QAbstractButton::toggled, m_view, &pqTaskControlView::updateTaskCompletion); - formLayout->addRow(label, ctrl); - } - break; case "ActiveTaskDescription"_hash: - { - auto* description = new QLabel(); - smtk::task::Task::InformationOptions opt; - description->setObjectName("taskDescription"); - description->setText(QString::fromStdString(task ? task->information(opt) : "")); - description->setWordWrap(true); // Allow line breaks so panel is not forced to be crazy wide. - layout->addWidget(description); - } - break; - case "ObjectControl"_hash: + this->addTaskDescription(layout, task, childSpec); + break; + case "ActiveTaskStatus"_hash: + this->addTaskStatus(formLayout, task, childSpec); + break; + case "RepresentationControl"_hash: + this->addRepresentationControl(formLayout, task, childSpec); break; case "OperationControl"_hash: + this->addOperationControl(layout, task, childSpec); break; default: return false; @@ -189,8 +319,7 @@ pqTaskControlView::~pqTaskControlView() bool pqTaskControlView::isEmpty() const { - // TODO - return false; + return m_p->numChildren == 0; } bool pqTaskControlView::isValid() const @@ -258,6 +387,92 @@ void pqTaskControlView::updateTaskCompletion(bool completed) } } +void pqTaskControlView::toggleVisibility(const std::string& name, bool show) +{ + if (m_p->m_currentTaskManager) + { + auto objMap = m_p->m_currentTaskManager->workflowObjects( + m_p->m_representationSpecs[name], m_p->m_currentTask); + auto repMap = smtk::paraview::representationsOfObjects(objMap); + for (const auto& entry : repMap) + { + bool needRender = false; + // Toggle the whole representation's visibility if either + // (1) entry.second contains a null pointer (indicating the resource itself + // is supposed to have its visibility changed) or + // (2) \a show is set to true (indicating that some components that were + // hidden should now be shown – which cannot happen without the + // resource-representation visibility being set to true). + if (show || representationObjectMapContainsResource(entry)) + { + if (entry.first->isVisible() != show) + { + entry.first->setVisible(show); + needRender = true; + } + } + auto pvDRep = dynamic_cast(entry.first); + if (!pvDRep) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Could not downcast representation " << entry.first + << " to an SMTK resource representation."); + continue; + } + // Now, tell the representation to toggle individual component visibilities. + for (const auto& obj : entry.second) + { + if (!obj) + { + continue; + } + if (auto comp = dynamic_cast(obj)->shared_from_this()) + { + needRender |= pvDRep->setVisibility(comp, show); + } + } + if (needRender) + { + entry.first->getView()->render(); + } + } + } +} + +void pqTaskControlView::updateOpacity(const std::string& name, double opacity) +{ + if (m_p->m_currentTaskManager) + { + auto objMap = m_p->m_currentTaskManager->workflowObjects( + m_p->m_representationSpecs[name], m_p->m_currentTask); + auto repMap = smtk::paraview::representationsOfObjects(objMap); + for (const auto& entry : repMap) + { + bool needRender = false; + // Toggle the whole representation's visibility if either + // (1) entry.second contains a null pointer (indicating the resource itself + // is supposed to have its visibility changed) or + // (2) \a show is set to true (indicating that some components that were + // hidden should now be shown – which cannot happen without the + // resource-representation visibility being set to true). + if (representationObjectMapContainsResource(entry)) + { + pqSMAdaptor::setElementProperty(entry.first->getProxy()->GetProperty("Opacity"), opacity); + entry.first->getProxy()->UpdateVTKObjects(); + needRender = true; + } + // TODO: Handle per-block opacity settings for components. + // Iterate over entry.second, find component inside the + // pqSMTKRepresentation and set per-block opacity. + if (needRender) + { + entry.first->getView()->render(); + } + } + } +} + void pqTaskControlView::buildUI() { this->createWidget(); @@ -326,6 +541,9 @@ void pqTaskControlView::createWidget() void pqTaskControlView::updateWithActiveTask(smtk::task::Task* task) { + m_p->m_currentTaskManager = task ? task->manager() : nullptr; + m_p->m_currentTask = task; + smtk::view::ConfigurationPtr view = this->configuration(); if (!view) { diff --git a/smtk/extension/paraview/project/pqTaskControlView.h b/smtk/extension/paraview/project/pqTaskControlView.h index d125299b36..64463f7e1d 100644 --- a/smtk/extension/paraview/project/pqTaskControlView.h +++ b/smtk/extension/paraview/project/pqTaskControlView.h @@ -10,8 +10,9 @@ #ifndef smtk_extension_pqTaskControlView_h #define smtk_extension_pqTaskControlView_h -#include "smtk/extension/paraview/project/smtkPQProjectExtModule.h" +#include "smtk/extension/paraview/project/smtkPQProjectExtModule.h" // For export. #include "smtk/extension/qt/qtBaseView.h" +#include "smtk/view/Configuration.h" // For API. namespace smtk { @@ -51,6 +52,8 @@ public Q_SLOTS: void onShowCategory() override; void returnToDiagram(); void updateTaskCompletion(bool completed); + void toggleVisibility(const std::string& name, bool show); + void updateOpacity(const std::string& name, double opacity); protected: void buildUI() override; diff --git a/smtk/task/Manager.cxx b/smtk/task/Manager.cxx index 8d973e38c5..aab352568f 100644 --- a/smtk/task/Manager.cxx +++ b/smtk/task/Manager.cxx @@ -9,6 +9,9 @@ //========================================================================= #include "smtk/task/Manager.h" +#include "smtk/task/ObjectsInRoles.h" + +#include "smtk/project/Project.h" #include "smtk/operation/Operation.h" @@ -20,10 +23,133 @@ #include "smtk/io/Logger.h" +using namespace smtk::string::literals; + namespace smtk { namespace task { +namespace // anonymous +{ + +bool resourceMatch( + const std::string& filter, + smtk::resource::PersistentObject* object, + smtk::resource::Resource*& rsrc) +{ + if (auto* resource = dynamic_cast(object)) + { + rsrc = resource; + if (filter == "*" || rsrc->matchesType(filter)) + { + return true; + } + } + else if (auto* comp = dynamic_cast(object)) + { + if ((rsrc = comp->parentResource())) + { + if (filter == "*" || rsrc->matchesType(filter)) + { + return true; + } + } + } + return false; +} + +bool componentMatch( + const std::string& filter, + smtk::resource::PersistentObject* object, + smtk::resource::Resource* rsrc) +{ + // An empty filter string indicates only resources are allowed. + if (filter.empty()) + { + return (object == rsrc); + } + // "*" allows any component: + else if (filter == "*") + { + // Assume that if object is not a pointer to the parent resource, + // it must be a component: + return (object != rsrc); + } + // We have a non-trivial filter string; we must have a component. + if (auto* comp = dynamic_cast(object)) + { + auto query = rsrc->queryOperation(filter); + return query ? query(*comp) : false; + } + return false; +} + +bool filterObject(const nlohmann::json::array_t& filter, smtk::resource::PersistentObject* object) +{ + for (const auto& filterTuple : filter) + { + try + { + if (filterTuple.is_array() && filterTuple.size() == 2) + { + smtk::resource::Resource* rsrc{ nullptr }; + if (resourceMatch(filterTuple[0].get(), object, rsrc)) + { + auto compSpec = filterTuple[1].is_null() ? "" : filterTuple[1].get(); + if (componentMatch(compSpec, object, rsrc)) + { + return true; + } + } + } + } + catch (nlohmann::json::exception& e) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Cannot process filter \"" << filterTuple.dump() << "\";" << e.what()); + continue; + } + } + return false; +} + +nlohmann::json::array_t filtersForSpec(const smtk::view::Configuration::Component& spec) +{ + nlohmann::json::array_t filters; + for (const auto& filter : spec.children()) + { + if (filter.name() != "Filter") + { + smtkWarningMacro( + smtk::io::Logger::instance(), "Unhandled child \"" << filter.name() << "\"."); + continue; + } + std::string rsrcMatch; + if (!filter.attribute("Resource", rsrcMatch)) + { + smtkWarningMacro( + smtk::io::Logger::instance(), "No resource in \"" << filter.name() << "\". Skipping."); + continue; + } + std::string compMatch; + filter.attribute("Component", compMatch); + nlohmann::json::array_t fspec{ rsrcMatch }; + //NOLINTNEXTLINE(modernize-use-emplace) + if (!compMatch.empty()) + { + fspec.push_back(compMatch); + } + else + { + fspec.push_back(std::nullptr_t()); + } + filters.emplace_back(fspec); + } + return filters; +} + +} // anonymous namespace constexpr const char* const Manager::type_name; @@ -149,6 +275,232 @@ smtk::resource::Resource* Manager::resource() const return m_parent; } +Manager::ResourceObjectMap Manager::workflowObjects(const nlohmann::json& spec, Task* task) +{ + // Filter objects by the \a spec. + ResourceObjectMap objectMap; + + nlohmann::json source; + nlohmann::json filter; + if (spec.contains("source")) + { + source = spec.at("source"); + if (!source.is_object() || !source.contains("type")) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Spec source must be a dictionary with 'type' key, " + "got \"" + << source.dump() << "\""); + return objectMap; + } + } + else + { + source = { { "type", "project resources" } }; + } + + if (spec.contains("filter")) + { + filter = spec.at("filter"); + if (!filter.is_array()) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Spec filter must be an array, got \"" << filter.dump() << "\"."); + return objectMap; + } + } + else + { + // Accept any resource or component: + filter = nlohmann::json::array_t({ { "*", "*" }, { "*", nullptr } }); + } + + auto sourceType = source["type"].get(); + std::unordered_set objects; + switch (sourceType.id()) + { + default: + smtkErrorMacro( + smtk::io::Logger::instance(), "Unknown source type \"" << sourceType.data() << "\"."); + // fall through + case "project resources"_hash: + { + auto* project = dynamic_cast(this->resource()); + if (!project) + { + return objectMap; + } + + for (const auto& resource : project->resources()) + { + if (filterObject(filter, resource.get())) + { + objects.insert(resource.get()); + } + } + } + break; + case "active task port"_hash: + { + auto* currentTask = task ? task : this->active().task(); + if (!currentTask || !source.contains("port")) + { + smtkErrorMacro(smtk::io::Logger::instance(), "No active task or no \"port\" specified."); + return objectMap; + } + + // We are asked to find objects on a port of the (now) active task. + // Determine whether we are filtering on role or not. + smtk::string::Token sourceRole; + if (source.contains("role")) + { + sourceRole = source.at("role").get(); + } + // Find the correct port: + const auto& taskPortMap = currentTask->ports(); + auto it = taskPortMap.find(source["port"]); + if (it == taskPortMap.end()) + { + return objectMap; + } + // Fetch data from the port. We only understand ObjectsInRoles at this point: + auto portData = it->second->parent()->portData(it->second); + if (portData) + { + if (auto objectsInRoles = std::dynamic_pointer_cast(portData)) + { + for (const auto& entry : objectsInRoles->data()) + { + if (sourceRole.valid() && entry.first != sourceRole) + { // Skip objects in unrequested roles. + continue; + } + // Filter objects as requested. + for (const auto& object : entry.second) + { + if (filterObject(filter, object)) + { + objects.insert(object); + } + } + } + } + else + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Unhandled port data type \"" << portData->typeName() << "\"."); + } + } + } + break; + } + + for (const auto& object : objects) + { + smtk::resource::Resource* resource = dynamic_cast(object); + if (auto* comp = dynamic_cast(object)) + { + resource = comp->parentResource(); + } + if (!resource || !this->isResourceRelevant(resource, filter)) + { + continue; + } + objectMap[resource].insert(object == resource ? nullptr : object); + } + + return objectMap; +} + +Manager::ResourceObjectMap Manager::workflowObjects( + const smtk::view::Configuration::Component& spec, + Task* task) +{ + // To avoid dueling implementations, we'll convert \a spec into JSON and pass + // it to the variant above. + nlohmann::json jsonSpec; + nlohmann::json::array_t controls; + for (const auto& entry : spec.children()) + { + smtk::string::Token tagName = entry.name(); + switch (tagName.id()) + { + case "ActiveTaskPort"_hash: + { + auto portName = entry.attributeAsString("Port"); + auto roleName = entry.attributeAsString("Role"); + if (portName.empty()) + { + smtkErrorMacro(smtk::io::Logger::instance(), "Missing a port name."); + continue; + } + nlohmann::json sourceSpec{ { "type", "active task port" }, { "port", portName } }; + if (!roleName.empty()) + { + sourceSpec["role"] = roleName; + } + auto filters = filtersForSpec(entry); + if (!filters.empty()) + { + sourceSpec["filters"] = filters; + } + jsonSpec["source"] = sourceSpec; + } + break; + case "ProjectResources"_hash: + { + nlohmann::json sourceSpec{ { "type", "project resources" } }; + auto filters = filtersForSpec(entry); + if (!filters.empty()) + { + sourceSpec["filters"] = filters; + } + jsonSpec["source"] = sourceSpec; + } + break; + case "Control"_hash: + { + auto controlType = entry.attributeAsString("Type"); + if (controlType.empty()) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "Control tag must provide a Type attribute."); + continue; + } + controls.emplace_back(controlType); + } + break; + default: + { + smtkWarningMacro( + smtk::io::Logger::instance(), + "Unhandled specification tag <" << tagName.data() << ">. Skipping."); + } + } + } + if (!controls.empty()) + { + jsonSpec["controls"] = controls; + } + auto objMap = this->workflowObjects(jsonSpec, task); + return objMap; +} + +bool Manager::isResourceRelevant( + const std::shared_ptr& resource, + const nlohmann::json& filter) +{ + return filterObject(filter, resource.get()); +} + +bool Manager::isResourceRelevant(smtk::resource::Resource* resource, const nlohmann::json& filter) +{ + return filterObject(filter, resource); +} + int Manager::handleOperation( const smtk::operation::Operation& op, smtk::operation::EventType event, diff --git a/smtk/task/Manager.h b/smtk/task/Manager.h index 9dd682795e..bff8796971 100644 --- a/smtk/task/Manager.h +++ b/smtk/task/Manager.h @@ -30,6 +30,8 @@ #include "smtk/task/Task.h" #include "smtk/task/adaptor/Instances.h" +#include "smtk/view/Configuration.h" + #include "nlohmann/json.hpp" #include @@ -82,6 +84,13 @@ public: Manager(const Manager&) = delete; void operator=(const Manager&) = delete; + /// A map of objects grouped by their parent resource. + /// + /// If a resource itself is intended for selection, one + /// entry of its target set of objects will be the null pointer. + using ResourceObjectMap = + std::map>; + /// Managed instances of Task objects (and a registry of Task classes). using TaskInstances = smtk::task::Instances; @@ -132,6 +141,35 @@ public: /// If this manager is owned by a resource (typically a project), return it. smtk::resource::Resource* resource() const; + /// Given a filter \a spec, return a set of objects grouped by their parent resources. + /// + /// The objects are drawn – according to the \a spec – from either ports of the active + /// task or resources owned by the parent of this task manager (assuming it is a project). + /// If a resource itself is intended to be part of the result, one of the object pointers + /// in that resource's target set will be the null pointer. + /// + /// The variant that accepts a JSON \a spec is for use inside task-manager style. + /// The variant that accepts a Configuration::Component \a spec is for use inside + /// view-configuration XML. + /// The two forms of \a spec are similar but not identical in order to improve + /// readability and reduce maintenance overhead. + ResourceObjectMap workflowObjects(const nlohmann::json& spec, Task* task = nullptr); + ResourceObjectMap workflowObjects( + const smtk::view::Configuration::Component& spec, + Task* task = nullptr); + + /// Return true if the \a resource matches the filter \a spec provided. + bool isResourceRelevant( + const std::shared_ptr& resource, + const nlohmann::json& spec); + bool isResourceRelevant(smtk::resource::Resource* resource, const nlohmann::json& spec); + bool isResourceRelevant( + const std::shared_ptr& resource, + const smtk::view::Configuration::Component& spec); + bool isResourceRelevant( + smtk::resource::Resource* resource, + const smtk::view::Configuration::Component& spec); + /// Return a gallery of Task Worklets Gallery& gallery() { return m_gallery; } const Gallery& gallery() const { return m_gallery; } -- GitLab From 1660de1f3bd279df8b5b7f6e2a9d6647a223695d Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sat, 15 Mar 2025 13:52:42 -0400 Subject: [PATCH 05/41] Hint to select all imported markup data. --- smtk/markup/operators/Import.cxx | 9 +++++++++ smtk/markup/operators/Import.sbt | 1 + 2 files changed, 10 insertions(+) diff --git a/smtk/markup/operators/Import.cxx b/smtk/markup/operators/Import.cxx index 5770b9cbe4..14497caf18 100644 --- a/smtk/markup/operators/Import.cxx +++ b/smtk/markup/operators/Import.cxx @@ -18,6 +18,8 @@ #include "smtk/extension/vtk/io/ImportAsVTKData.h" +#include "smtk/view/Selection.h" + #include "smtk/attribute/Attribute.h" #include "smtk/attribute/ComponentItem.h" #include "smtk/attribute/FileItem.h" @@ -28,6 +30,7 @@ #include "smtk/attribute/ResourceItem.h" #include "smtk/attribute/StringItem.h" +#include "smtk/operation/Hints.h" #include "smtk/operation/MarkGeometry.h" #include "smtk/resource/Manager.h" @@ -414,6 +417,12 @@ Import::Result Import::operateInternal() { m_result->findInt("outcome")->setValue(0, static_cast(Import::Outcome::SUCCEEDED)); m_result->findResource("resourcesCreated")->appendValue(resource); + + auto created = m_result->findComponent("created"); + std::set importedComponents; + importedComponents.insert(created->begin(), created->end()); + smtk::operation::addSelectionHint( + m_result, importedComponents, smtk::view::SelectionAction::UNFILTERED_REPLACE); } return m_result; } diff --git a/smtk/markup/operators/Import.sbt b/smtk/markup/operators/Import.sbt index d22f03234d..06855f1bbd 100644 --- a/smtk/markup/operators/Import.sbt +++ b/smtk/markup/operators/Import.sbt @@ -24,6 +24,7 @@ + -- GitLab From fd3b84f3245cca6711365c5498443d33b438a78f Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 17 Mar 2025 18:02:47 -0400 Subject: [PATCH 06/41] =?UTF-8?q?Rename=20API=20to=20use=20unitSystem=20in?= =?UTF-8?q?stead=20of=20unitsSystem=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … to match terminology used by engineers. --- .../notes/resource-unit-system-typo.rst | 21 +++++++++++++ doc/userguide/attribute/concepts.rst | 2 +- smtk/attribute/Attribute.cxx | 2 +- smtk/attribute/Definition.cxx | 20 ++++++------- smtk/attribute/Definition.h | 14 +++++++-- smtk/attribute/DoubleItem.cxx | 30 +++++++++---------- smtk/attribute/DoubleItemDefinition.cxx | 16 +++++----- smtk/attribute/GroupItemDefinition.cxx | 8 ++--- smtk/attribute/GroupItemDefinition.h | 11 +++++-- smtk/attribute/ItemDefinition.cxx | 4 +-- smtk/attribute/ItemDefinition.h | 18 +++++++---- smtk/attribute/ReferenceItemDefinition.cxx | 6 ++-- smtk/attribute/ReferenceItemDefinition.h | 7 ++++- smtk/attribute/Resource.cxx | 12 ++++---- smtk/attribute/Resource.h | 9 ++++-- smtk/attribute/ValueItem.cxx | 8 ++--- smtk/attribute/ValueItemDefinition.cxx | 16 +++++----- smtk/attribute/ValueItemDefinition.h | 11 +++++-- smtk/extension/qt/qtAttribute.cxx | 2 +- smtk/extension/qt/qtDoubleUnitsLineEdit.cxx | 4 +-- smtk/extension/qt/qtInputsItem.cxx | 10 +++---- smtk/resource/Resource.cxx | 10 +++---- smtk/resource/Resource.h | 15 ++++++++-- 23 files changed, 161 insertions(+), 95 deletions(-) create mode 100644 doc/release/notes/resource-unit-system-typo.rst diff --git a/doc/release/notes/resource-unit-system-typo.rst b/doc/release/notes/resource-unit-system-typo.rst new file mode 100644 index 0000000000..815a23e621 --- /dev/null +++ b/doc/release/notes/resource-unit-system-typo.rst @@ -0,0 +1,21 @@ +Resource system +=============== + +Unit system API name change +--------------------------- + +The methods to set or get the system of units for a resource have +been renamed to match the terminology most engineers use: "unit" +is singular since there is one system holding all of the units. + ++ :smtk:`unitsSystem()` ++ :smtk:`setUnitsSystem()`_ for a simple example of using units with Attributes and Definitions. diff --git a/smtk/attribute/Attribute.cxx b/smtk/attribute/Attribute.cxx index 0d9e37b01c..244c780cd8 100644 --- a/smtk/attribute/Attribute.cxx +++ b/smtk/attribute/Attribute.cxx @@ -1208,7 +1208,7 @@ bool Attribute::setLocalUnits(const std::string& newUnits) return false; } - const auto& unitSys = m_definition->unitsSystem(); + const auto& unitSys = m_definition->unitSystem(); // Can't determine if the units are compatible w/o units system if (!unitSys) { diff --git a/smtk/attribute/Definition.cxx b/smtk/attribute/Definition.cxx index 5a233cfd24..d533adef5b 100644 --- a/smtk/attribute/Definition.cxx +++ b/smtk/attribute/Definition.cxx @@ -690,19 +690,19 @@ bool Definition::addItemDefinition(smtk::attribute::ItemDefinitionPtr cdef) std::size_t n = m_itemDefs.size(); m_itemDefs.push_back(cdef); m_itemDefPositions[cdef->name()] = static_cast(n); - this->setItemDefinitionUnitsSystem(cdef); + this->setItemDefinitionUnitSystem(cdef); this->updateDerivedDefinitions(); return true; } -void Definition::setItemDefinitionUnitsSystem( +void Definition::setItemDefinitionUnitSystem( const smtk::attribute::ItemDefinitionPtr& itemDef) const { - const auto& defUnitsSystem = this->unitsSystem(); + const auto& defUnitSystem = this->unitSystem(); auto attRes = this->attributeResource(); - if (defUnitsSystem) + if (defUnitSystem) { - itemDef->setUnitsSystem(defUnitsSystem); + itemDef->setUnitSystem(defUnitSystem); } } @@ -914,15 +914,15 @@ void Definition::applyAdvanceLevels( } } -const std::shared_ptr& Definition::unitsSystem() const +const std::shared_ptr& Definition::unitSystem() const { - static std::shared_ptr nullUnitsSystem; + static std::shared_ptr nullUnitSystem; auto attRes = this->attributeResource(); if (attRes) { - return attRes->unitsSystem(); + return attRes->unitSystem(); } - return nullUnitsSystem; + return nullUnitSystem; } bool Definition::setLocalUnits(const std::string& newUnits, bool force) @@ -933,7 +933,7 @@ bool Definition::setLocalUnits(const std::string& newUnits, bool force) m_localUnits = newUnits; return true; } - const auto& unitSys = this->unitsSystem(); + const auto& unitSys = this->unitSystem(); if (!unitSys) { return false; // There is no unit system diff --git a/smtk/attribute/Definition.h b/smtk/attribute/Definition.h index 5881045f0d..0b003d5332 100644 --- a/smtk/attribute/Definition.h +++ b/smtk/attribute/Definition.h @@ -21,6 +21,7 @@ #include "smtk/attribute/Tag.h" #include "smtk/common/Categories.h" +#include "smtk/common/Deprecation.h" #include "smtk/model/EntityRef.h" //for EntityRef version of canBeAssociated #include "smtk/model/EntityTypeBits.h" // for BitFlags type @@ -359,7 +360,7 @@ public: { std::size_t n = m_itemDefs.size(); item = SharedTypes::RawPointerType::New(name); - this->setItemDefinitionUnitsSystem(item); + this->setItemDefinitionUnitSystem(item); m_itemDefs.push_back(item); m_itemDefPositions[name] = static_cast(n); this->updateDerivedDefinitions(); @@ -481,7 +482,9 @@ public: bool setLocalUnits(const std::string& newUnits, bool force = false); /// \brief Gets the system of units used by this definition. - const std::shared_ptr& unitsSystem() const; + const std::shared_ptr& unitSystem() const; + SMTK_DEPRECATED_IN_NEXT("Use unitSystem() instead.") + const std::shared_ptr& unitsSystem() const { return this->unitSystem(); } protected: friend class smtk::attribute::Resource; @@ -510,7 +513,12 @@ protected: const unsigned int& readLevelFromParent, const unsigned int& writeLevelFromParent); - void setItemDefinitionUnitsSystem(const smtk::attribute::ItemDefinitionPtr& itemDef) const; + void setItemDefinitionUnitSystem(const smtk::attribute::ItemDefinitionPtr& itemDef) const; + SMTK_DEPRECATED_IN_NEXT("Use setItemDefinitionUnitSystem() instead.") + void setItemDefinitionUnitsSystem(const smtk::attribute::ItemDefinitionPtr& itemDef) const + { + this->setItemDefinitionUnitSystem(itemDef); + } smtk::attribute::WeakResourcePtr m_resource; smtk::common::UUID m_id; diff --git a/smtk/attribute/DoubleItem.cxx b/smtk/attribute/DoubleItem.cxx index 5e36badb0f..170dfc78cc 100644 --- a/smtk/attribute/DoubleItem.cxx +++ b/smtk/attribute/DoubleItem.cxx @@ -207,26 +207,26 @@ bool DoubleItem::setValue(std::size_t element, const double& val, const std::str const std::string& myUnitStr = this->supportedUnits(); if (myUnitStr != valUnitStr) { - auto unitsSystem = this->definition()->unitsSystem(); + auto unitSystem = this->definition()->unitSystem(); // Is there a units system specified? - if (!unitsSystem) + if (!unitSystem) { return false; // we can not convert units } bool status; - auto myUnits = unitsSystem->unit(myUnitStr, &status); + auto myUnits = unitSystem->unit(myUnitStr, &status); if (!status) { return false; // Could not find the base's units } - auto valUnits = unitsSystem->unit(valUnitStr, &status); + auto valUnits = unitSystem->unit(valUnitStr, &status); if (!status) { return false; // Could not find vals' units } units::Measurement m(val, valUnits); - auto newM = unitsSystem->convert(m, myUnits, &status); + auto newM = unitSystem->convert(m, myUnits, &status); if (!status) { return false; // could not convert @@ -268,7 +268,7 @@ bool DoubleItem::setValueFromString(std::size_t element, const std::string& val) return false; // badly formatted string } - auto unitsSystem = this->definition()->unitsSystem(); + auto unitSystem = this->definition()->unitSystem(); const std::string& myUnitStr = this->supportedUnits(); units::Unit myUnit; @@ -276,11 +276,11 @@ bool DoubleItem::setValueFromString(std::size_t element, const std::string& val) // item has known units. Note that we don't need to do conversion // if the value does not have units specified bool convert = false; - if (unitsSystem && (!(myUnitStr.empty() || valUnitsStr.empty()))) + if (unitSystem && (!(myUnitStr.empty() || valUnitsStr.empty()))) { // If we have a units System, let's see if the base units // are valid? - myUnit = unitsSystem->unit(myUnitStr, &convert); + myUnit = unitSystem->unit(myUnitStr, &convert); } double convertedVal; @@ -304,7 +304,7 @@ bool DoubleItem::setValueFromString(std::size_t element, const std::string& val) // We can convert units bool status; - auto valMeasure = unitsSystem->measurement(val, &status); + auto valMeasure = unitSystem->measurement(val, &status); if (!status) { // Could not parse the value @@ -312,7 +312,7 @@ bool DoubleItem::setValueFromString(std::size_t element, const std::string& val) } if (!valMeasure.m_units.dimensionless()) { - auto convertedMeasure = unitsSystem->convert(valMeasure, myUnit, &status); + auto convertedMeasure = unitSystem->convert(valMeasure, myUnit, &status); if (!status) { return false; @@ -519,9 +519,9 @@ double DoubleItem::value(std::size_t element, smtk::io::Logger& log) const return eval; // no conversion needed } // We need to convert to the units of the item - auto unitsSystem = this->definition()->unitsSystem(); + auto unitSystem = this->definition()->unitSystem(); // Is there a units system specified? - if (!unitsSystem) + if (!unitSystem) { smtkErrorMacro( log, @@ -531,7 +531,7 @@ double DoubleItem::value(std::size_t element, smtk::io::Logger& log) const return 0.0; } bool status; - auto myUnits = unitsSystem->unit(this->units(), &status); + auto myUnits = unitSystem->unit(this->units(), &status); if (!status) { smtkErrorMacro( @@ -541,7 +541,7 @@ double DoubleItem::value(std::size_t element, smtk::io::Logger& log) const << " are not supported for conversion."); return 0.0; } - auto exUnits = unitsSystem->unit(exStr, &status); + auto exUnits = unitSystem->unit(exStr, &status); if (!status) { smtkErrorMacro( @@ -553,7 +553,7 @@ double DoubleItem::value(std::size_t element, smtk::io::Logger& log) const } units::Measurement m(eval, exUnits); - auto newM = unitsSystem->convert(m, myUnits, &status); + auto newM = unitSystem->convert(m, myUnits, &status); if (!status) { smtkErrorMacro( diff --git a/smtk/attribute/DoubleItemDefinition.cxx b/smtk/attribute/DoubleItemDefinition.cxx index 6dbb2bacc6..9964adc256 100644 --- a/smtk/attribute/DoubleItemDefinition.cxx +++ b/smtk/attribute/DoubleItemDefinition.cxx @@ -124,21 +124,21 @@ bool DoubleItemDefinition::setDefaultValue( return false; } // do we have a unit system? - if (m_unitsSystem) + if (m_unitSystem) { // Are the units compatible? bool status; - auto defUnit = m_unitsSystem->unit(m_units, &status); + auto defUnit = m_unitSystem->unit(m_units, &status); if (!status) { return false; // Could not find the definition's units } - auto valUnit = m_unitsSystem->unit(units, &status); + auto valUnit = m_unitSystem->unit(units, &status); if (!status) { return false; // Could not find the default vals' units } - auto converter = m_unitsSystem->convert(valUnit, defUnit); + auto converter = m_unitSystem->convert(valUnit, defUnit); if (!converter) { return false; // Could not find a conversion between the units @@ -257,11 +257,11 @@ bool DoubleItemDefinition::reevaluateDefaults() std::string units; units::Unit defUnit; bool convert = false; - if (m_unitsSystem) + if (m_unitSystem) { // If we have an units System, lets see if the definition's units // are valid? - defUnit = m_unitsSystem->unit(m_units, &convert); + defUnit = m_unitSystem->unit(m_units, &convert); } // Can we not do conversion? @@ -302,7 +302,7 @@ bool DoubleItemDefinition::reevaluateDefaults() double convertedVal; for (std::size_t i = 0; i < m_defaultValuesAsStrings.size(); i++) { - auto valMeasure = m_unitsSystem->measurement(m_defaultValuesAsStrings[i], &status); + auto valMeasure = m_unitSystem->measurement(m_defaultValuesAsStrings[i], &status); if (!status) { // Could not parse the value @@ -310,7 +310,7 @@ bool DoubleItemDefinition::reevaluateDefaults() } if (!valMeasure.m_units.dimensionless()) { - auto convertedMeasure = m_unitsSystem->convert(valMeasure, defUnit, &status); + auto convertedMeasure = m_unitSystem->convert(valMeasure, defUnit, &status); if (!status) { return false; diff --git a/smtk/attribute/GroupItemDefinition.cxx b/smtk/attribute/GroupItemDefinition.cxx index 61e05b590b..eda96072f6 100644 --- a/smtk/attribute/GroupItemDefinition.cxx +++ b/smtk/attribute/GroupItemDefinition.cxx @@ -57,7 +57,7 @@ bool GroupItemDefinition::addItemDefinition(smtk::attribute::ItemDefinitionPtr c { cdef->setIsOptional(true); } - cdef->setUnitsSystem(m_unitsSystem); + cdef->setUnitSystem(m_unitSystem); return true; } @@ -238,12 +238,12 @@ bool GroupItemDefinition::removeItemDefinition(ItemDefinitionPtr itemDef) return true; } -void GroupItemDefinition::setUnitsSystem(const shared_ptr& unitsSystem) +void GroupItemDefinition::setUnitSystem(const shared_ptr& unitSystem) { - m_unitsSystem = unitsSystem; + m_unitSystem = unitSystem; for (const auto& item : m_itemDefs) { - item->setUnitsSystem(m_unitsSystem); + item->setUnitSystem(m_unitSystem); } } diff --git a/smtk/attribute/GroupItemDefinition.h b/smtk/attribute/GroupItemDefinition.h index 47ca4c1f39..3f6f6b0b02 100644 --- a/smtk/attribute/GroupItemDefinition.h +++ b/smtk/attribute/GroupItemDefinition.h @@ -65,8 +65,8 @@ public: { item->setIsOptional(true); } - // We need to get a pointer to the base Item class to set the unitsSystem - static_cast(item.get())->setUnitsSystem(m_unitsSystem); + // We need to get a pointer to the base Item class to set the unitSystem + static_cast(item.get())->setUnitSystem(m_unitSystem); m_itemDefs.push_back(item); m_itemDefPositions[inName] = static_cast(n); } @@ -159,7 +159,12 @@ protected: void applyAdvanceLevels( const unsigned int& readLevelFromParent, const unsigned int& writeLevelFromParent) override; - void setUnitsSystem(const shared_ptr& unitsSystem) override; + void setUnitSystem(const shared_ptr& unitSystem) override; + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem() instead.") + void setUnitsSystem(const shared_ptr& unitsSystem) override + { + this->setUnitSystem(unitsSystem); + } std::vector m_itemDefs; std::map m_itemDefPositions; std::vector m_labels; diff --git a/smtk/attribute/ItemDefinition.cxx b/smtk/attribute/ItemDefinition.cxx index 5923c35eeb..f7874a611c 100644 --- a/smtk/attribute/ItemDefinition.cxx +++ b/smtk/attribute/ItemDefinition.cxx @@ -157,7 +157,7 @@ bool ItemDefinition::removeTag(const std::string& name) return false; } -void ItemDefinition::setUnitsSystem(const shared_ptr& unitsSystem) +void ItemDefinition::setUnitSystem(const shared_ptr& unitSystem) { - m_unitsSystem = unitsSystem; + m_unitSystem = unitSystem; } diff --git a/smtk/attribute/ItemDefinition.h b/smtk/attribute/ItemDefinition.h index 1ac7f9eb00..b8e1c435c3 100644 --- a/smtk/attribute/ItemDefinition.h +++ b/smtk/attribute/ItemDefinition.h @@ -23,6 +23,7 @@ #include "smtk/attribute/Item.h" // For Item Types. #include "smtk/attribute/Tag.h" #include "smtk/common/Categories.h" +#include "smtk/common/Deprecation.h" #include #include @@ -167,8 +168,10 @@ public: bool removeTag(const std::string& name); ///@} - ///\brief Return the unitsSystem of the Definition - const shared_ptr& unitsSystem() const { return m_unitsSystem; } + ///\brief Return the unitSystem of the Definition + const shared_ptr& unitSystem() const { return m_unitSystem; } + SMTK_DEPRECATED_IN_NEXT("Use unitSystem() instead.") + const shared_ptr& unitsSystem() const { return m_unitSystem; } virtual smtk::attribute::ItemPtr buildItem(Attribute* owningAttribute, int itemPosition) const = 0; @@ -189,10 +192,15 @@ protected: const unsigned int& readLevelFromParent, const unsigned int& writeLevelFromParent); - ///\brief Set the unitsSystem of the Definition + ///\brief Set the unitSystem of the Definition /// /// Note that this should be done before units are specified in the Definition - virtual void setUnitsSystem(const shared_ptr& unitsSystem); + virtual void setUnitSystem(const shared_ptr& unitSystem); + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem() instead.") + virtual void setUnitsSystem(const shared_ptr& unitsSystem) + { + this->setUnitSystem(unitsSystem); + } int m_version; bool m_isOptional; @@ -207,7 +215,7 @@ protected: unsigned int m_advanceLevel[2]; attribute::Tags m_tags; smtk::common::Categories::CombinationMode m_combinationMode; - std::shared_ptr m_unitsSystem; + std::shared_ptr m_unitSystem; private: // constant value that should never be changed diff --git a/smtk/attribute/ReferenceItemDefinition.cxx b/smtk/attribute/ReferenceItemDefinition.cxx index 7500579272..7abd002d1b 100644 --- a/smtk/attribute/ReferenceItemDefinition.cxx +++ b/smtk/attribute/ReferenceItemDefinition.cxx @@ -611,13 +611,13 @@ std::size_t ReferenceItemDefinition::testConditionals(PersistentObjectPtr& objec return s_invalidIndex; } -void ReferenceItemDefinition::setUnitsSystem(const shared_ptr& unitsSystem) +void ReferenceItemDefinition::setUnitSystem(const shared_ptr& unitSystem) { - m_unitsSystem = unitsSystem; + m_unitSystem = unitSystem; for (const auto& item : m_itemDefs) { - item.second->setUnitsSystem(m_unitsSystem); + item.second->setUnitSystem(m_unitSystem); } } } // namespace attribute diff --git a/smtk/attribute/ReferenceItemDefinition.h b/smtk/attribute/ReferenceItemDefinition.h index 1acd7c6296..34f0e2d52b 100644 --- a/smtk/attribute/ReferenceItemDefinition.h +++ b/smtk/attribute/ReferenceItemDefinition.h @@ -265,7 +265,12 @@ protected: const smtk::common::Categories::Stack& inheritedFromParent, smtk::common::Categories& inheritedToParent) override; - void setUnitsSystem(const shared_ptr& unitsSystem) override; + void setUnitSystem(const shared_ptr& unitSystem) override; + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem() instead.") + void setUnitsSystem(const shared_ptr& unitsSystem) override + { + this->setUnitSystem(unitsSystem); + } /// Debug method that returns a description concerning the validity of the component std::string componentValidityCheck(const smtk::resource::Component* comp) const; diff --git a/smtk/attribute/Resource.cxx b/smtk/attribute/Resource.cxx index afab3d5e37..850b2d108c 100644 --- a/smtk/attribute/Resource.cxx +++ b/smtk/attribute/Resource.cxx @@ -54,7 +54,7 @@ Resource::Resource(const smtk::common::UUID& myID, smtk::resource::ManagerPtr ma : smtk::resource::DerivedFrom(myID, manager) { queries().registerQueries(); - m_unitsSystem = units::System:: + m_unitSystem = units::System:: createWithDefaults(); // Create a unit system with some prefixes, dimensions, and units. // Do not display resource views in the attribute panel automatically. @@ -66,7 +66,7 @@ Resource::Resource(smtk::resource::ManagerPtr manager) : smtk::resource::DerivedFrom(manager) { queries().registerQueries(); - m_unitsSystem = units::System:: + m_unitSystem = units::System:: createWithDefaults(); // Create a unit system with some prefixes, dimensions, and units. // Do not display resource views in the attribute panel automatically. @@ -84,20 +84,20 @@ Resource::~Resource() } } -bool Resource::setUnitsSystem(const shared_ptr& unitsSystem) +bool Resource::setUnitSystem(const shared_ptr& unitSystem) { - if (m_unitsSystem == unitsSystem) + if (m_unitSystem == unitSystem) { return true; } // Since changing units systems can invalidate Item Definitions and Items // we only can change systems when there are no definitions or if there was not // currently an units system specified. - if (m_unitsSystem && !m_definitions.empty()) + if (m_unitSystem && !m_definitions.empty()) { return false; } - m_unitsSystem = unitsSystem; + m_unitSystem = unitSystem; return true; } diff --git a/smtk/attribute/Resource.h b/smtk/attribute/Resource.h index cae76c0d69..d52032ac62 100644 --- a/smtk/attribute/Resource.h +++ b/smtk/attribute/Resource.h @@ -106,7 +106,12 @@ public: ~Resource() override; - bool setUnitsSystem(const shared_ptr& unitsSystem) override; + bool setUnitSystem(const shared_ptr& unitSystem) override; + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem instead.") + bool setUnitsSystem(const shared_ptr& unitsSystem) override + { + return this->setUnitSystem(unitsSystem); + } smtk::attribute::DefinitionPtr createDefinition( const std::string& typeName, @@ -443,7 +448,7 @@ public: /// /// If \a options has copyTemplateData() set to true, then this resource's /// Definition instances will be copied to the output resources. - /// In addition, unitsSystem and Analysis information is copied. + /// In addition, unitSystem and Analysis information is copied. std::shared_ptr clone( smtk::resource::CopyOptions& options) const override; diff --git a/smtk/attribute/ValueItem.cxx b/smtk/attribute/ValueItem.cxx index cfcb55df33..d30160239a 100644 --- a/smtk/attribute/ValueItem.cxx +++ b/smtk/attribute/ValueItem.cxx @@ -340,7 +340,7 @@ bool ValueItem::isAcceptable(const smtk::attribute::AttributePtr& exp) const return false; } - const auto& unitSys = def->unitsSystem(); + const auto& unitSys = def->unitSystem(); // Can't determine if the units are compatible w/o units system if (!unitSys) { @@ -976,11 +976,11 @@ std::string ValueItem::supportedUnits() const { bool status = false; auto munits = this->units(); - auto unitsSystem = m_definition->unitsSystem(); + auto unitSystem = m_definition->unitSystem(); - if (!(munits.empty() || (unitsSystem == nullptr))) + if (!(munits.empty() || (unitSystem == nullptr))) { - auto myUnit = unitsSystem->unit(munits, &status); + auto myUnit = unitSystem->unit(munits, &status); } return (status ? munits : std::string()); } diff --git a/smtk/attribute/ValueItemDefinition.cxx b/smtk/attribute/ValueItemDefinition.cxx index 5eacf6f6d9..8d9772eb9e 100644 --- a/smtk/attribute/ValueItemDefinition.cxx +++ b/smtk/attribute/ValueItemDefinition.cxx @@ -521,7 +521,7 @@ bool ValueItemDefinition::addItemDefinition(smtk::attribute::ItemDefinitionPtr c return false; } m_itemDefs[cdef->name()] = cdef; - cdef->setUnitsSystem(m_unitsSystem); + cdef->setUnitSystem(m_unitSystem); return true; } @@ -575,13 +575,13 @@ std::vector ValueItemDefinition::relevantEnums( return result; } -void ValueItemDefinition::setUnitsSystem(const shared_ptr& unitsSystem) +void ValueItemDefinition::setUnitSystem(const shared_ptr& unitSystem) { - m_unitsSystem = unitsSystem; + m_unitSystem = unitSystem; for (const auto& item : m_itemDefs) { - item.second->setUnitsSystem(m_unitsSystem); + item.second->setUnitSystem(m_unitSystem); } } @@ -605,10 +605,10 @@ bool ValueItemDefinition::isDiscreteIndexValid(int index) const bool ValueItemDefinition::hasSupportedUnits() const { - if (!(m_units.empty() || (m_unitsSystem == nullptr))) + if (!(m_units.empty() || (m_unitSystem == nullptr))) { bool status; - auto defUnit = m_unitsSystem->unit(m_units, &status); + auto defUnit = m_unitSystem->unit(m_units, &status); return status; } return false; @@ -617,9 +617,9 @@ bool ValueItemDefinition::hasSupportedUnits() const std::string ValueItemDefinition::supportedUnits() const { bool status = false; - if (!(m_units.empty() || (m_unitsSystem == nullptr))) + if (!(m_units.empty() || (m_unitSystem == nullptr))) { - auto defUnit = m_unitsSystem->unit(m_units, &status); + auto defUnit = m_unitSystem->unit(m_units, &status); } return (status ? m_units : std::string()); } diff --git a/smtk/attribute/ValueItemDefinition.h b/smtk/attribute/ValueItemDefinition.h index 07223e3b2d..0005f60da1 100644 --- a/smtk/attribute/ValueItemDefinition.h +++ b/smtk/attribute/ValueItemDefinition.h @@ -248,8 +248,8 @@ public: } item = SharedTypes::RawPointerType::New(idName); m_itemDefs[item->name()] = item; - // We need to get a pointer to the base Item class to set the unitsSystem - static_cast(item.get())->setUnitsSystem(m_unitsSystem); + // We need to get a pointer to the base Item class to set the unitSystem + static_cast(item.get())->setUnitSystem(m_unitSystem); return item; } @@ -290,7 +290,12 @@ protected: const unsigned int& readLevelFromParent, const unsigned int& writeLevelFromParent) override; - void setUnitsSystem(const shared_ptr& unitsSystem) override; + void setUnitSystem(const shared_ptr& unitSystem) override; + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem() instead.") + void setUnitsSystem(const shared_ptr& unitsSystem) override + { + this->setUnitSystem(unitsSystem); + } virtual void updateDiscreteValue() = 0; bool m_hasDefault; diff --git a/smtk/extension/qt/qtAttribute.cxx b/smtk/extension/qt/qtAttribute.cxx index 17fc3c95f5..d5ec6b31cb 100644 --- a/smtk/extension/qt/qtAttribute.cxx +++ b/smtk/extension/qt/qtAttribute.cxx @@ -286,7 +286,7 @@ void qtAttribute::createBasicLayout(bool includeAssociations) QString baseUnits = (att->definition()->units() == "*") ? att->units().c_str() : att->definition()->units().c_str(); auto* unitsWidget = - new qtUnitsLineEdit(baseUnits, att->definition()->unitsSystem(), uiManager, unitFrame); + new qtUnitsLineEdit(baseUnits, att->definition()->unitSystem(), uiManager, unitFrame); std::string unitsWidgetName = att->name() + "UnitsLineWidget"; unitsWidget->setObjectName(unitsWidgetName.c_str()); unitsLayout->addWidget(unitsWidget); diff --git a/smtk/extension/qt/qtDoubleUnitsLineEdit.cxx b/smtk/extension/qt/qtDoubleUnitsLineEdit.cxx index 346e28ed1c..e2180bf1b2 100644 --- a/smtk/extension/qt/qtDoubleUnitsLineEdit.cxx +++ b/smtk/extension/qt/qtDoubleUnitsLineEdit.cxx @@ -167,7 +167,7 @@ qtDoubleUnitsLineEdit* qtDoubleUnitsLineEdit::checkAndCreate( } // Get units system - auto unitSystem = dItem->definition()->unitsSystem(); + auto unitSystem = dItem->definition()->unitSystem(); if (unitSystem == nullptr) { return nullptr; @@ -302,7 +302,7 @@ void qtDoubleUnitsLineEdit::onTextEdited() auto dItem = m_inputsItem->itemAs(); auto dUnits = dItem->units(); - auto unitSystem = dItem->definition()->unitsSystem(); + auto unitSystem = dItem->definition()->unitSystem(); // Parsing the Item's unit string bool parsedOK = false; diff --git a/smtk/extension/qt/qtInputsItem.cxx b/smtk/extension/qt/qtInputsItem.cxx index 7b70552d52..8589a48f5e 100644 --- a/smtk/extension/qt/qtInputsItem.cxx +++ b/smtk/extension/qt/qtInputsItem.cxx @@ -744,9 +744,9 @@ void qtInputsItem::showExpressionResultWidgets( auto itemUnits = item->units(); if (!itemUnits.empty()) { - auto unitsSystem = item->definition()->unitsSystem(); + auto unitSystem = item->definition()->unitSystem(); bool parsed = false; - unitsSystem->unit(itemUnits, &parsed); + unitSystem->unit(itemUnits, &parsed); if (parsed) { displayText = QString("%1 %2").arg(text).arg(itemUnits.c_str()); @@ -981,11 +981,11 @@ QFrame* qtInputsItem::createLabelFrame( if (addUnitsLabel && !vitemDef->isDiscrete()) { // Check if units are "valid" - const auto& unitsSystem = vitemDef->unitsSystem(); - if (unitsSystem) + const auto& unitSystem = vitemDef->unitSystem(); + if (unitSystem) { bool unitsParsed = false; - units::Unit defUnit = unitsSystem->unit(valUnits, &unitsParsed); + units::Unit defUnit = unitSystem->unit(valUnits, &unitsParsed); addUnitsLabel = addUnitsLabel && (!unitsParsed); } } diff --git a/smtk/resource/Resource.cxx b/smtk/resource/Resource.cxx index de90b2788a..926ba5804c 100644 --- a/smtk/resource/Resource.cxx +++ b/smtk/resource/Resource.cxx @@ -225,9 +225,9 @@ void Resource::setClean(bool state) m_clean = state; } -bool Resource::setUnitsSystem(const shared_ptr& unitsSystem) +bool Resource::setUnitSystem(const shared_ptr& unitSystem) { - m_unitsSystem = unitsSystem; + m_unitSystem = unitSystem; return true; } @@ -285,15 +285,15 @@ void Resource::copyUnitSystem( // Do not set a unit system. break; case CopyOptions::CopyType::Shallow: - this->setUnitsSystem(rsrc->unitsSystem()); + this->setUnitSystem(rsrc->unitSystem()); break; case CopyOptions::CopyType::Deep: { - if (auto unitSys = rsrc->unitsSystem()) + if (auto unitSys = rsrc->unitSystem()) { nlohmann::json spec = unitSys; shared_ptr unitCopy = units::System::createFromSpec(spec.dump()); - this->setUnitsSystem(unitCopy); + this->setUnitSystem(unitCopy); } } break; diff --git a/smtk/resource/Resource.h b/smtk/resource/Resource.h index c7d1c9706b..2d7ea5bc63 100644 --- a/smtk/resource/Resource.h +++ b/smtk/resource/Resource.h @@ -13,6 +13,7 @@ #include "smtk/CoreExports.h" +#include "smtk/common/Deprecation.h" #include "smtk/common/UUID.h" #include "smtk/resource/Component.h" @@ -331,9 +332,17 @@ public: /// unit systems. /// \brief Sets the system of units used by this resource. - virtual bool setUnitsSystem(const shared_ptr& unitsSystem); + virtual bool setUnitSystem(const shared_ptr& unitSystem); + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem() instead.") + virtual bool setUnitsSystem(const shared_ptr& unitsSystem) + { + return this->setUnitSystem(unitsSystem); + } + /// \brief Gets the system of units used by this resource. - const shared_ptr& unitsSystem() const { return m_unitsSystem; } + const shared_ptr& unitSystem() const { return m_unitSystem; } + SMTK_DEPRECATED_IN_NEXT("Use setUnitSystem() instead.") + const shared_ptr& unitsSystem() const { return m_unitSystem; } ///@} ///@name Resource Templates @@ -489,7 +498,7 @@ protected: void copyLinks(const std::shared_ptr& rsrc, const CopyOptions& options); WeakManagerPtr m_manager; - std::shared_ptr m_unitsSystem; + std::shared_ptr m_unitSystem; private: /// Instances of this internal class are passed to resource::Manager to -- GitLab From 78f7de59acd74d7eef996b2bf00bec54ad12336a Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 18 Mar 2025 10:09:00 -0400 Subject: [PATCH 07/41] =?UTF-8?q?Ability=20to=20"blank"=20geometry=20of=20?= =?UTF-8?q?markup::SpatialData=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … and its subclasses. This allows for workflows to preserve loaded data in its original form without displaying it so that transformed version(s) may be rendered. --- smtk/extension/vtk/markup/Geometry.cxx | 10 +++++ smtk/markup/SpatialData.cxx | 36 ++++++++++++++++++ smtk/markup/SpatialData.h | 15 ++++++++ smtk/markup/pybind11/CMakeLists.txt | 8 +++- smtk/markup/pybind11/PybindMarkup.cxx | 4 ++ smtk/markup/pybind11/PybindSpatialData.h | 35 +++++++++++++++++ smtk/markup/pybind11/PybindUnstructuredData.h | 36 ++++++++++++++++++ smtk/markup/testing/python/markupResource.py | 38 ++++++++++++++++++- 8 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 smtk/markup/pybind11/PybindSpatialData.h create mode 100644 smtk/markup/pybind11/PybindUnstructuredData.h diff --git a/smtk/extension/vtk/markup/Geometry.cxx b/smtk/extension/vtk/markup/Geometry.cxx index fb0700fe05..8239b19679 100644 --- a/smtk/extension/vtk/markup/Geometry.cxx +++ b/smtk/extension/vtk/markup/Geometry.cxx @@ -61,6 +61,16 @@ void Geometry::queryGeometry(const smtk::resource::PersistentObject::Ptr& obj, C entry.m_geometry = nullptr; + // Check component-wide blanking + if (auto* spatial = dynamic_cast(component.get())) + { + if (spatial->isBlanked()) + { + entry.m_generation = Invalid; + return; + } + } + if (auto* image = dynamic_cast(component.get())) { auto shape = image->shapeData(); diff --git a/smtk/markup/SpatialData.cxx b/smtk/markup/SpatialData.cxx index 2b2cf3e3f5..515ac56eff 100644 --- a/smtk/markup/SpatialData.cxx +++ b/smtk/markup/SpatialData.cxx @@ -47,5 +47,41 @@ AssignedIds* SpatialData::domainExtent(smtk::string::Token domainName) const return nullptr; } +bool SpatialData::isBlanked() const +{ + const auto& boolProps = this->properties().get(); + if (!boolProps.contains("_blanked")) + { + return false; + } + return boolProps.at("_blanked"); +} + +bool SpatialData::setBlanking(bool shouldBlank) +{ + if (!this->properties().contains("_blanked")) + { + // Already not blanked? Do nothing. + if (!shouldBlank) + { + return false; + } + this->properties().emplace("_blanked", true); + return true; + } + // Already blanked? Do nothing + if (this->properties().at("_blanked") == shouldBlank) + { + return false; + } + if (shouldBlank) + { + this->properties().emplace("_blanked", true); + return true; + } + this->properties().erase("_blanked"); + return true; +} + } // namespace markup } // namespace smtk diff --git a/smtk/markup/SpatialData.h b/smtk/markup/SpatialData.h index b4b62b7986..ffe15a02fa 100644 --- a/smtk/markup/SpatialData.h +++ b/smtk/markup/SpatialData.h @@ -69,6 +69,21 @@ public: virtual AssignedIds* domainExtent(Domain* domain) const; virtual AssignedIds* domainExtent(smtk::string::Token domainName) const; + /// Return whether or not this node has its geometry blanked (i.e., not rendered). + bool isBlanked() const; + + /// Set whether or not this node has its geometry blanked. + /// + /// Blanking a node's geometry is usually performed by an operation that wishes to + /// keep the source geometry around but also permanently transform it in some reversible + /// or editable way. While the markup session also allows transform properties + /// attached to nodes to affect rendering, this can cause issues for downstream filters + /// that need access to the transformed geometry (and do not wish to perform the transform + /// themselves). + /// + /// This method returns true when the node's blanking state was modified. + bool setBlanking(bool shouldBlank); + protected: }; diff --git a/smtk/markup/pybind11/CMakeLists.txt b/smtk/markup/pybind11/CMakeLists.txt index 245c4015a5..8bd8126cc7 100644 --- a/smtk/markup/pybind11/CMakeLists.txt +++ b/smtk/markup/pybind11/CMakeLists.txt @@ -9,7 +9,13 @@ pybind11_add_module(${library_name} target_include_directories(${library_name} PUBLIC $ ) -target_link_libraries(${library_name} LINK_PUBLIC smtkCore smtkMarkup) +target_link_libraries(${library_name} + LINK_PUBLIC + smtkMarkup + smtkCore + VTK::CommonCore + VTK::WrappingPythonCore +) set_target_properties(${library_name} PROPERTIES CXX_VISIBILITY_PRESET hidden diff --git a/smtk/markup/pybind11/PybindMarkup.cxx b/smtk/markup/pybind11/PybindMarkup.cxx index 6869fad85e..29ef5e1ecc 100644 --- a/smtk/markup/pybind11/PybindMarkup.cxx +++ b/smtk/markup/pybind11/PybindMarkup.cxx @@ -23,8 +23,10 @@ using PySharedPtrClass = py::class_, Args...>; #include "PybindResource.h" #include "PybindComponent.h" +#include "PybindSpatialData.h" #include "PybindDomain.h" #include "PybindDomainMap.h" +#include "PybindUnstructuredData.h" #include "smtk/resource/Manager.h" @@ -44,4 +46,6 @@ PYBIND11_MODULE(_smtkPybindMarkup, markup) auto smtk_markup_Component = pybind11_init_smtk_markup_Component(markup); auto smtk_markup_Domain = pybind11_init_smtk_markup_Domain(markup); auto smtk_markup_DomainMap = pybind11_init_smtk_markup_DomainMap(markup); + auto smtk_markup_SpatialData = pybind11_init_smtk_markup_SpatialData(markup); + auto smtk_markup_UnstructuredData = pybind11_init_smtk_markup_UnstructuredData(markup); } diff --git a/smtk/markup/pybind11/PybindSpatialData.h b/smtk/markup/pybind11/PybindSpatialData.h new file mode 100644 index 0000000000..049eac0c5d --- /dev/null +++ b/smtk/markup/pybind11/PybindSpatialData.h @@ -0,0 +1,35 @@ +//========================================================================= +// 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_markup_SpatialData_h +#define pybind_smtk_markup_SpatialData_h + +#include + +#include "smtk/markup/SpatialData.h" + +#include "smtk/common/UUID.h" +#include "smtk/common/pybind11/PybindUUIDTypeCaster.h" + +namespace py = pybind11; + +inline PySharedPtrClass< smtk::markup::SpatialData> pybind11_init_smtk_markup_SpatialData(py::module &m) +{ + PySharedPtrClass< smtk::markup::SpatialData, smtk::markup::Component> instance(m, "SpatialData"); + instance + .def("setBlanking", &smtk::markup::SpatialData::setBlanking, py::arg("shouldBlank")) + .def("isBlanked", &smtk::markup::SpatialData::isBlanked) + .def_static("CastTo", [](const std::shared_ptr& obj) + { return std::dynamic_pointer_cast(obj); }) + ; + return instance; +} + +#endif diff --git a/smtk/markup/pybind11/PybindUnstructuredData.h b/smtk/markup/pybind11/PybindUnstructuredData.h new file mode 100644 index 0000000000..0541d8de08 --- /dev/null +++ b/smtk/markup/pybind11/PybindUnstructuredData.h @@ -0,0 +1,36 @@ +//========================================================================= +// 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_markup_UnstructuredData_h +#define pybind_smtk_markup_UnstructuredData_h + +#include + +#include "smtk/markup/UnstructuredData.h" + +#include "smtk/extension/vtk/pybind11/PybindVTKTypeCaster.h" + +#include "smtk/common/UUID.h" +#include "smtk/common/pybind11/PybindUUIDTypeCaster.h" + +namespace py = pybind11; + +inline PySharedPtrClass< smtk::markup::UnstructuredData> pybind11_init_smtk_markup_UnstructuredData(py::module &m) +{ + PySharedPtrClass< smtk::markup::UnstructuredData, smtk::markup::SpatialData> instance(m, "UnstructuredData"); + instance + .def("shape", &smtk::markup::UnstructuredData::shape) + .def_static("CastTo", [](const std::shared_ptr& obj) + { return std::dynamic_pointer_cast(obj); }) + ; + return instance; +} + +#endif diff --git a/smtk/markup/testing/python/markupResource.py b/smtk/markup/testing/python/markupResource.py index e9c42e67ad..6b13109416 100644 --- a/smtk/markup/testing/python/markupResource.py +++ b/smtk/markup/testing/python/markupResource.py @@ -19,6 +19,27 @@ print('Node Types\n ' + '\n '.join([xx.data() for xx in resource.nodeTypes()])) print('Arc Types\n ' + '\n '.join([xx.data() for xx in resource.arcTypes()])) +expectedNodeset = set([ + 'URL', 'UnstructuredData', 'Comment', 'Plane', 'Cone', 'Label', 'DiscreteGeometry', + 'Ontology', 'Field', 'AnalyticShape', 'SideSet', 'Box', 'OntologyIdentifier', + 'Subset', 'NodeSet', 'Landmark', 'SpatialData', 'ImageData', 'Component', 'Sphere', + 'Feature', 'Group']) +fqnsNodes = {smtk.string.Token('smtk::markup::' + xx) + for xx in expectedNodeset} +if fqnsNodes != set(resource.nodeTypes()): + print('Error: unexpected nodeset.') + raise RuntimeError('Unexpected nodeset') + +expectedArcset = set([ + 'URLsToData', 'ReferencesToPrimaries', 'LabelsToSubjects', 'OntologyIdentifiersToSubtypes', + 'URLsToImportedData', 'OntologyIdentifiersToIndividuals', 'FieldsToShapes', + 'OntologyToIdentifiers', 'GroupsToMembers', 'BoundariesToShapes']) +fqnsArcs = {smtk.string.Token('smtk::markup::arcs::' + xx) + for xx in expectedArcset} +if fqnsArcs != set(resource.arcTypes()): + print('Error: unexpected arcset.') + raise RuntimeError('Unexpected arcset') + # Create one node of every type nodes = [resource.createNodeOfType(nodeTypeName) for nodeTypeName in resource.nodeTypes()] @@ -48,5 +69,18 @@ if not didConnect: resource.dump('') -print(resource.domains()) -print(resource.domains().keys()) +# print(resource.domains()) +print('Domains\n ' + '\n '.join([xx.data() + for xx in resource.domains().keys()])) + +print('Test blanking') +nodeToBlank = resource.createNodeOfType( + smtk.string.Token('smtk::markup::UnstructuredData')) +bbefore = nodeToBlank.isBlanked() +print(' Is blanked initially?', nodeToBlank.isBlanked()) +nodeToBlank.setBlanking(True) +print(' Is blanked finally?', nodeToBlank.isBlanked()) +bafter = nodeToBlank.isBlanked() +print(' Shape?', nodeToBlank.shape()) +if bbefore or not bafter: + raise ('Blanking incorrect') -- GitLab From bb938fcd12d0d4166f13fa519f0fb2667879a793 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 19 Mar 2025 15:50:37 -0400 Subject: [PATCH 08/41] Add a default length unit to markup resources. --- smtk/markup/Resource.cxx | 25 ++++++++++++++++++++ smtk/markup/Resource.h | 17 +++++++++++++ smtk/markup/Traits.h | 1 + smtk/markup/json/jsonResource.cxx | 13 ++++++++++ smtk/markup/testing/python/markupResource.py | 1 - 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/smtk/markup/Resource.cxx b/smtk/markup/Resource.cxx index 336a312226..f912ab8809 100644 --- a/smtk/markup/Resource.cxx +++ b/smtk/markup/Resource.cxx @@ -23,6 +23,8 @@ #include "smtk/common/Paths.h" #include "smtk/common/StringUtil.h" +#include "units/System.h" + namespace smtk { namespace markup @@ -137,6 +139,29 @@ std::function Resource::queryOperation( return smtk::resource::filter::Filter(query); } +bool Resource::setLengthUnit(const std::string& unit) +{ + auto unitSys = this->unitSystem(); + if (unit == m_lengthUnit || !unitSys) + { + return false; + } + bool ok = true; + auto uu = unitSys->unit(unit, &ok); + if (!ok) + { + // Unit must be a valid unit. + return false; + } + if (uu.dimension() != unitSys->dimension("L")) + { + // Units must be length. + return false; + } + m_lengthUnit = unit; + return true; +} + void Resource::initialize() { using namespace smtk::string::literals; // for ""_token diff --git a/smtk/markup/Resource.h b/smtk/markup/Resource.h index f65cbd153f..6e16182dd1 100644 --- a/smtk/markup/Resource.h +++ b/smtk/markup/Resource.h @@ -98,6 +98,21 @@ public: */ static DomainFactory& domainFactory() { return s_domainFactory; } + /**\brief Set/get the default units of length for geometric data in this resource. + * + * When the default is empty (which it is by default), no units are provided. + * Otherwise, all geometric data is assumed to be in these units. + * + * You should not modify the length unit while geometric data exists inside + * the resource. + * + * If there is no valid unit system or if \a unit is invalid + * (i.e., not a valid unit or with dimensions other than length), + * setLengthUnit() will fail. + */ + std::string lengthUnit() const { return m_lengthUnit; } + bool setLengthUnit(const std::string& unit); + protected: friend class Component; @@ -111,6 +126,8 @@ protected: DomainMap m_domains; static DomainFactory s_domainFactory; + + std::string m_lengthUnit; }; template diff --git a/smtk/markup/Traits.h b/smtk/markup/Traits.h index f6af4bfee1..1edda98870 100644 --- a/smtk/markup/Traits.h +++ b/smtk/markup/Traits.h @@ -147,6 +147,7 @@ struct SMTKMARKUP_EXPORT BoundariesToShapes static constexpr graph::OwnershipSemantics semantics = graph::OwnershipSemantics::ToNodeOwnsFromNode; }; + } // namespace arcs // Forward-declare domain types diff --git a/smtk/markup/json/jsonResource.cxx b/smtk/markup/json/jsonResource.cxx index d219b27319..ad2dea5b93 100644 --- a/smtk/markup/json/jsonResource.cxx +++ b/smtk/markup/json/jsonResource.cxx @@ -217,6 +217,12 @@ void to_json(nlohmann::json& j, const smtk::markup::Resource::Ptr& resource) j["domains"] = domainData; } + // Record length unit (if one exists). + if (!resource->lengthUnit().empty()) + { + j["length-unit"] = resource->lengthUnit(); + } + // Record string-token hashes. // Some nodes may use string tokens, so we must serialize that map if it exists. auto& tokenManager = smtk::string::Token::manager(); @@ -280,6 +286,13 @@ void from_json(const nlohmann::json& jj, smtk::markup::Resource::Ptr& resource) } } + // Deserialize length unit. + auto jLengthUnit = jj.find("length-unit"); + if (jLengthUnit != jj.end()) + { + resource->setLengthUnit(jLengthUnit->get()); + } + // Deserialize domains auto jDomains = jj.find("domains"); if (jDomains != jj.end()) diff --git a/smtk/markup/testing/python/markupResource.py b/smtk/markup/testing/python/markupResource.py index 6b13109416..e30ac45078 100644 --- a/smtk/markup/testing/python/markupResource.py +++ b/smtk/markup/testing/python/markupResource.py @@ -81,6 +81,5 @@ print(' Is blanked initially?', nodeToBlank.isBlanked()) nodeToBlank.setBlanking(True) print(' Is blanked finally?', nodeToBlank.isBlanked()) bafter = nodeToBlank.isBlanked() -print(' Shape?', nodeToBlank.shape()) if bbefore or not bafter: raise ('Blanking incorrect') -- GitLab From 3242619865a29d7a6c370e5c2e939b6a06f5b6ab Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 19 Mar 2025 16:16:25 -0400 Subject: [PATCH 09/41] =?UTF-8?q?Add=20`smtk::common::Paths::uniquePath()`?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … which calls the function of the same name in boost::filesystem. --- smtk/common/Paths.cxx | 5 +++++ smtk/common/Paths.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/smtk/common/Paths.cxx b/smtk/common/Paths.cxx index f3eeae0c17..a33386c789 100644 --- a/smtk/common/Paths.cxx +++ b/smtk/common/Paths.cxx @@ -265,6 +265,11 @@ std::string Paths::uniquePath() return boost::filesystem::unique_path().string(); } +std::string Paths::uniquePath(const std::string& modelPath) +{ + return boost::filesystem::unique_path(modelPath).string(); +} + /**\brief Return the best guess at the directory containing the current process's executable. */ std::string Paths::executableDirectory() diff --git a/smtk/common/Paths.h b/smtk/common/Paths.h index 2b6a77d688..7a4c21f156 100644 --- a/smtk/common/Paths.h +++ b/smtk/common/Paths.h @@ -64,6 +64,8 @@ public: static std::string replaceFilename(const std::string& path, const std::string& newFilename); static std::string tempDirectory(); static std::string uniquePath(); + /// The \a modelPath must have percent (%) signs that will be replaced with random characters. + static std::string uniquePath(const std::string& modelPath); std::string executableDirectory(); std::string toplevelDirectory(); -- GitLab From 88a647de13a19d965ea6417c747df146de43d829 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 19 Mar 2025 16:17:22 -0400 Subject: [PATCH 10/41] =?UTF-8?q?Add=20`smtk::resource::Resource::absolute?= =?UTF-8?q?Location()`=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … that recursively ascends parent resources and/or examines the current working directory to properly compute the absolute location on disk of a resource. --- smtk/resource/Resource.cxx | 33 +++++++++++++++++++++++++++++++++ smtk/resource/Resource.h | 23 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/smtk/resource/Resource.cxx b/smtk/resource/Resource.cxx index 926ba5804c..9523d7f866 100644 --- a/smtk/resource/Resource.cxx +++ b/smtk/resource/Resource.cxx @@ -200,6 +200,39 @@ bool Resource::setLocation(const std::string& myLocation) return false; } +std::string Resource::absoluteLocation(bool createDir) const +{ + auto url = this->location(); + if (!smtk::common::Paths::isRelative(url)) + { + if (createDir) + { + auto containingDir = smtk::common::Paths::directory(url); + smtk::common::Paths::createDirectory(containingDir); + } + return url; + } + const auto* parent = this->parentResource(); + if (parent && parent != this) + { + auto parentUrl = parent->absoluteLocation(); + // The parent URL contains the filename of the parent resource. Strip that: + auto parentDir = smtk::common::Paths::directory(parentUrl); + url = parentDir + "/" + url; + } + else + { + auto workDir = smtk::common::Paths::currentDirectory(); + url = workDir + "/" + url; + } + if (createDir) + { + auto containingDir = smtk::common::Paths::directory(url); + smtk::common::Paths::createDirectory(containingDir); + } + return url; +} + std::string Resource::name() const { if (m_name.empty()) diff --git a/smtk/resource/Resource.h b/smtk/resource/Resource.h index 2d7ea5bc63..f82ecc2e52 100644 --- a/smtk/resource/Resource.h +++ b/smtk/resource/Resource.h @@ -171,6 +171,28 @@ public: /// This may change when a user chooses to "Save As…" a different filename. const std::string& location() const { return m_location; } virtual bool setLocation(const std::string& location); + + /// As a convenience, fetch the absolute path to a resource from its location. + /// + /// For URLs with a protocol (e.g., "https://foo.com/resource.smtk") or local + /// paths with a leading slash (e.g., "/foo/resource.smtk"), this is exactly + /// the resource's location() string. + /// + /// However, if a resource has a relative path, we must look to see whether + /// it has a parent resource; if so, this method is recursively called on + /// the parent to find its absolute path so that the relative path of the + /// child resource can be determined. + /// + /// When a resource (the immediate one or any parent) has a relative location + /// but no parent, then the current working directory is used to determine + /// its absolute path. + /// + /// As an added convenience, you may pass "true" as a second argument to this + /// method to have it create the containing directory of the resource's + /// location for you. This is for use by Write operations. + /// + /// \sa smtk::common::Paths::isRelative, smtk::common::Paths::currentDirectory + std::string absoluteLocation(bool createDir = false) const; ///@} ///@name Naming @@ -603,6 +625,7 @@ SMTKCORE_NO_EXPORT QueryType& queryForObject(const PersistentObject& object) } throw query::BadTypeError(smtk::common::typeName()); } + } // namespace resource } // namespace smtk -- GitLab From 85822609c80a97bc8678dfe1c0a65bda852b2919 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 19 Mar 2025 16:18:32 -0400 Subject: [PATCH 11/41] =?UTF-8?q?Add/update=20`generateSummary()`=20method?= =?UTF-8?q?s=20of=20write=20operators=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … to avoid superfluous console spam when write operations complete. --- smtk/attribute/operators/Write.cxx | 16 +++++++++++++++- smtk/attribute/operators/Write.h | 1 + smtk/markup/operators/Write.cxx | 11 +++++++++++ smtk/markup/operators/Write.h | 1 + smtk/operation/operators/WriteResource.cxx | 3 +-- smtk/project/operators/Write.cxx | 11 +++++++++++ smtk/project/operators/Write.h | 1 + 7 files changed, 41 insertions(+), 3 deletions(-) diff --git a/smtk/attribute/operators/Write.cxx b/smtk/attribute/operators/Write.cxx index 1e554a7960..0c44298d85 100644 --- a/smtk/attribute/operators/Write.cxx +++ b/smtk/attribute/operators/Write.cxx @@ -52,7 +52,10 @@ Write::Result Write::operateInternal() } { - std::ofstream file(resource->location()); + // Ensure parent directory exists. It may not if we are part of a project + // that has organized us into an unrealized subdirectory. + auto location = resource->absoluteLocation(/*create directories*/ true); + std::ofstream file(location); if (!file.good()) { smtkErrorMacro(log(), "Unable to open \"" << resource->location() << "\" for writing."); @@ -82,6 +85,17 @@ void Write::markModifiedResources(Write::Result& /*unused*/) } } +void Write::generateSummary(Result& res) +{ + if (smtk::operation::outcome(res) != Outcome::SUCCEEDED) + { + this->Superclass::generateSummary(res); + } + auto resourceItem = this->parameters()->associations(); + auto resource = std::dynamic_pointer_cast(resourceItem->value()); + smtkInfoMacro(this->log(), "Wrote \"" << resource->location() << "\"."); +} + bool write( const smtk::resource::ResourcePtr& resource, const std::shared_ptr& managers) diff --git a/smtk/attribute/operators/Write.h b/smtk/attribute/operators/Write.h index 30ccd4b850..1925dee44c 100644 --- a/smtk/attribute/operators/Write.h +++ b/smtk/attribute/operators/Write.h @@ -31,6 +31,7 @@ protected: Result operateInternal() override; const char* xmlDescription() const override; void markModifiedResources(Result&) override; + void generateSummary(Result&) override; }; SMTKCORE_EXPORT bool write( diff --git a/smtk/markup/operators/Write.cxx b/smtk/markup/operators/Write.cxx index 2921f59e24..2bdd7ffe2f 100644 --- a/smtk/markup/operators/Write.cxx +++ b/smtk/markup/operators/Write.cxx @@ -203,6 +203,17 @@ void Write::markModifiedResources(Write::Result& /*unused*/) } } +void Write::generateSummary(Result& res) +{ + if (smtk::operation::outcome(res) != Outcome::SUCCEEDED) + { + this->Superclass::generateSummary(res); + } + auto resourceItem = this->parameters()->associations(); + auto resource = std::dynamic_pointer_cast(resourceItem->value()); + smtkInfoMacro(this->log(), "Wrote \"" << resource->location() << "\"."); +} + bool Write::writeData( const Component* dataNode, const std::string& filename, diff --git a/smtk/markup/operators/Write.h b/smtk/markup/operators/Write.h index 0f8dd40cf1..41e4d35cb2 100644 --- a/smtk/markup/operators/Write.h +++ b/smtk/markup/operators/Write.h @@ -38,6 +38,7 @@ protected: Result operateInternal() override; const char* xmlDescription() const override; void markModifiedResources(Result&) override; + void generateSummary(Result&) override; bool writeData(const Component* dataNode, const std::string& filename, smtk::string::Token mimeType); diff --git a/smtk/operation/operators/WriteResource.cxx b/smtk/operation/operators/WriteResource.cxx index 2d38d77190..7e4c6fe3c5 100644 --- a/smtk/operation/operators/WriteResource.cxx +++ b/smtk/operation/operators/WriteResource.cxx @@ -213,8 +213,7 @@ void WriteResource::generateSummary(WriteResource::Result& res) } else if (outcome == static_cast(smtk::operation::Operation::Outcome::SUCCEEDED)) { - msg << ": wrote \"" << resource->location() << "\""; - smtkInfoMacro(this->log(), msg.str()); + // Do nothing. Each child operation should provide a summary. } else { diff --git a/smtk/project/operators/Write.cxx b/smtk/project/operators/Write.cxx index 960e67a5c8..af50c76532 100644 --- a/smtk/project/operators/Write.cxx +++ b/smtk/project/operators/Write.cxx @@ -173,6 +173,17 @@ Write::Result Write::operateInternal() return result; } +void Write::generateSummary(Operation::Result& res) +{ + if (smtk::operation::outcome(res) != Outcome::SUCCEEDED) + { + this->Superclass::generateSummary(res); + } + auto resourceItem = this->parameters()->associations(); + auto resource = std::dynamic_pointer_cast(resourceItem->value()); + smtkInfoMacro(this->log(), "Wrote \"" << resource->location() << "\"."); +} + const char* Write::xmlDescription() const { return Write_xml; diff --git a/smtk/project/operators/Write.h b/smtk/project/operators/Write.h index 5a5c85f51f..e2c97f457b 100644 --- a/smtk/project/operators/Write.h +++ b/smtk/project/operators/Write.h @@ -34,6 +34,7 @@ public: protected: Result operateInternal() override; + void generateSummary(Operation::Result& res) override; const char* xmlDescription() const override; }; -- GitLab From 95e186fa1e0744262d30b961c25b2198ff52ec3c Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 20 Mar 2025 07:55:06 -0400 Subject: [PATCH 12/41] Provide python operations a way to run nested operations. --- smtk/operation/Operation.h | 2 + smtk/operation/pybind11/PybindOperation.h | 50 +++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/smtk/operation/Operation.h b/smtk/operation/Operation.h index 35df8ee437..9dead7e3de 100644 --- a/smtk/operation/Operation.h +++ b/smtk/operation/Operation.h @@ -40,6 +40,7 @@ class Helper; class ImportPythonOperation; class Manager; class Operation; +class PythonRunChild; using Handler = std::function&)>; @@ -228,6 +229,7 @@ public: }; protected: + friend class PythonRunChild; Operation(); /// Identify resources to lock, and whether to lock them for reading or writing. diff --git a/smtk/operation/pybind11/PybindOperation.h b/smtk/operation/pybind11/PybindOperation.h index b0dddeafa8..b9013291ff 100644 --- a/smtk/operation/pybind11/PybindOperation.h +++ b/smtk/operation/pybind11/PybindOperation.h @@ -22,6 +22,43 @@ #include "smtk/io/Logger.h" +namespace smtk +{ +namespace operation +{ + +/// A helper class that is a friend of smtk::operation::Operation so it +/// can construct a Key to run "child" (nested) operations. +class PythonRunChild +{ +public: + using Result = smtk::operation::Operation::Result; + using ObserverOption = smtk::operation::Operation::ObserverOption; + using LockOption = smtk::operation::Operation::LockOption; + using ParametersOption = smtk::operation::Operation::ParametersOption; + + PythonRunChild(smtk::operation::Operation* self) + : m_self(self) + { + } + + Result run( + const smtk::operation::Operation::Ptr& childOp, + ObserverOption observerOption, + LockOption lockOption, + ParametersOption paramsOption) + { + auto key = m_self->childKey(observerOption, lockOption, paramsOption); + return childOp->operate(key); + } + +protected: + smtk::operation::Operation* m_self{ nullptr }; +}; + +} // namespace operation +} // namespace smtk + namespace py = pybind11; inline PySharedPtrClass< smtk::operation::Operation, smtk::operation::PyOperation > pybind11_init_smtk_operation_Operation(py::module &m) @@ -102,6 +139,19 @@ inline PySharedPtrClass< smtk::operation::Operation, smtk::operation::PyOperatio .def("manager", &smtk::operation::Operation::manager) .def("managers", &smtk::operation::Operation::managers) .def("restoreTrace", (bool (smtk::operation::Operation::*)(::std::string const &)) &smtk::operation::Operation::restoreTrace) + .def("runChildOp", [](smtk::operation::Operation* self, const smtk::operation::Operation::Ptr& childOp, + smtk::operation::Operation::ObserverOption observerOption, + smtk::operation::Operation::LockOption lockOption, + smtk::operation::Operation::ParametersOption paramsOption) + { + smtk::operation::PythonRunChild runner(self); + auto result = runner.run(childOp, observerOption, lockOption, paramsOption); + return result; + }, + py::arg("childOp"), + py::arg("observerOption") = smtk::operation::Operation::ObserverOption::SkipObservers, + py::arg("lockOption") = smtk::operation::Operation::LockOption::ParentLocksOnly, + py::arg("paramsOption") = smtk::operation::Operation::ParametersOption::SkipValidation) ; py::enum_(instance, "Outcome") .value("UNABLE_TO_OPERATE", smtk::operation::Operation::Outcome::UNABLE_TO_OPERATE) -- GitLab From 9d619e3f58d06b1498f30f68e47c7c287fcdc5f2 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 20 Mar 2025 07:56:01 -0400 Subject: [PATCH 13/41] Provide python `values()` and `setValues()` for reference items. --- smtk/attribute/pybind11/PybindReferenceItem.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/smtk/attribute/pybind11/PybindReferenceItem.h b/smtk/attribute/pybind11/PybindReferenceItem.h index 9d04c5fba0..ae4adfc792 100644 --- a/smtk/attribute/pybind11/PybindReferenceItem.h +++ b/smtk/attribute/pybind11/PybindReferenceItem.h @@ -57,6 +57,21 @@ inline PySharedPtrClass< smtk::attribute::ReferenceItem, smtk::attribute::Item > .def("unset", &smtk::attribute::ReferenceItem::unset, py::arg("i") = 0) .def("valueAsString", (std::string (smtk::attribute::ReferenceItem::*)() const) &smtk::attribute::ReferenceItem::valueAsString) .def("valueAsString", (std::string (smtk::attribute::ReferenceItem::*)(::size_t) const) &smtk::attribute::ReferenceItem::valueAsString, py::arg("i")) + .def("setValues", [&](smtk::attribute::ReferenceItem* self, const std::vector& values) + { + return self->setValues(values.begin(), values.end()); + }, py::arg("values")) + .def("values", [&](smtk::attribute::ReferenceItem* self) -> std::vector + { + std::vector values; + std::size_t nn = self->numberOfValues(); + values.reserve(nn); + for (std::size_t ii = 0; ii < nn; ++ii) + { + values.push_back(self->isSet(ii) ? self->value(ii) : smtk::resource::PersistentObject::Ptr()); + } + return values; + }) ; return instance; } -- GitLab From 0d260915b387385f96b01160de2816016323db31 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 20 Mar 2025 08:01:40 -0400 Subject: [PATCH 14/41] Add python bindings for TrivialProducerAgent. Specifically, the static methods used to update its configuration. --- smtk/task/pybind11/PybindTask.cxx | 2 + smtk/task/pybind11/PybindTask.h | 1 + .../pybind11/PybindTrivialProducerAgent.h | 79 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 smtk/task/pybind11/PybindTrivialProducerAgent.h diff --git a/smtk/task/pybind11/PybindTask.cxx b/smtk/task/pybind11/PybindTask.cxx index 0abf159f54..3e0944bf75 100644 --- a/smtk/task/pybind11/PybindTask.cxx +++ b/smtk/task/pybind11/PybindTask.cxx @@ -35,6 +35,7 @@ using namespace nlohmann; #include "PybindState.h" #include "PybindSubmitOperationAgent.h" #include "PybindTask.h" +#include "PybindTrivialProducerAgent.h" #include "PybindWorklet.h" #include "PybindInstances.h" @@ -58,6 +59,7 @@ PYBIND11_MODULE(_smtkPybindTask, m) auto smtk_task_FillOutAttributesAgent = pybind11_init_smtk_task_FillOutAttributesAgent(m); auto smtk_task_SubmitOperationAgent = pybind11_init_smtk_task_SubmitOperationAgent(m); auto smtk_task_GatherObjectsAgent = pybind11_init_smtk_task_GatherObjectsAgent(m); + auto smtk_task_TrivialProducerAgent = pybind11_init_smtk_task_TrivialProducerAgent(m); auto smtk_task_Task = pybind11_init_smtk_task_Task(m); pybind11_init_smtk_task_State(m); pybind11_init_smtk_task_stateEnum(m); diff --git a/smtk/task/pybind11/PybindTask.h b/smtk/task/pybind11/PybindTask.h index 303a2fac91..58d9c0d8d1 100644 --- a/smtk/task/pybind11/PybindTask.h +++ b/smtk/task/pybind11/PybindTask.h @@ -34,6 +34,7 @@ inline PySharedPtrClass< smtk::task::Task, smtk::resource::Component > pybind11_ .def("setName", &smtk::task::Task::setName, py::arg("name")) .def("title", &smtk::task::Task::name) .def("setTitle", &smtk::task::Task::setName, py::arg("title")) + .def("ports", &smtk::task::Task::ports) .def("state", &smtk::task::Task::state) .def("agents", &smtk::task::Task::agents, py::return_value_policy::reference_internal) .def("children", &smtk::task::Task::children, py::return_value_policy::reference_internal) diff --git a/smtk/task/pybind11/PybindTrivialProducerAgent.h b/smtk/task/pybind11/PybindTrivialProducerAgent.h new file mode 100644 index 0000000000..0ec4527f82 --- /dev/null +++ b/smtk/task/pybind11/PybindTrivialProducerAgent.h @@ -0,0 +1,79 @@ +//========================================================================= +// 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_task_TrivialProducerAgent_h +#define pybind_smtk_task_TrivialProducerAgent_h + +#include + +#include "smtk/task/TrivialProducerAgent.h" + +#include "smtk/task/Port.h" + +namespace py = pybind11; + +inline py::class_< smtk::task::TrivialProducerAgent > pybind11_init_smtk_task_TrivialProducerAgent(py::module &m) +{ + py::class_< smtk::task::TrivialProducerAgent, smtk::task::Agent > instance(m, "TrivialProducerAgent"); + instance + .def_static("addObjectInRole", []( + smtk::task::Task* task, const std::string& agentName, const std::string& role, const smtk::resource::PersistentObject::Ptr& object) + { + return smtk::task::TrivialProducerAgent::addObjectInRole(task, agentName, role, object.get()); + }, + py::arg("task"), py::arg("agentName"), py::arg("role"), py::arg("object")) + .def_static("addObjectInRole", []( + smtk::task::Task* task, smtk::task::Port* port, const std::string& role, const smtk::resource::PersistentObject::Ptr& object) + { + return smtk::task::TrivialProducerAgent::addObjectInRole(task, port, role, object.get()); + }, + py::arg("task"), py::arg("port"), py::arg("role"), py::arg("object")) + .def_static("removeObjectFromRole", []( + smtk::task::Task* task, const std::string& agentName, const std::string& role, const smtk::resource::PersistentObject::Ptr& object) + { + return smtk::task::TrivialProducerAgent::removeObjectFromRole(task, agentName, role, object.get()); + }, + py::arg("task"), py::arg("agentName"), py::arg("role"), py::arg("object")) + .def_static("removeObjectFromRole", []( + smtk::task::Task* task, smtk::task::Port* port, const std::string& role, const smtk::resource::PersistentObject::Ptr& object) + { + return smtk::task::TrivialProducerAgent::removeObjectFromRole(task, port, role, object.get()); + }, + py::arg("task"), py::arg("port"), py::arg("role"), py::arg("object")) + .def_static("resetData", [](smtk::task::Task* task, const std::string& agentName) + { + return smtk::task::TrivialProducerAgent::resetData(task, agentName); + }, py::arg("task"), py::arg("agentName")) + .def_static("resetData", [](smtk::task::Task* task, smtk::task::Port* port) + { + return smtk::task::TrivialProducerAgent::resetData(task, port); + }, py::arg("task"), py::arg("port")) + .def("typeToken", &smtk::task::TrivialProducerAgent::typeToken) + .def("classHierarchy", &smtk::task::TrivialProducerAgent::classHierarchy) + .def("matchesType", &smtk::task::TrivialProducerAgent::matchesType, py::arg("candidate")) + .def("generationsFromBase", &smtk::task::TrivialProducerAgent::generationsFromBase, py::arg("base")) + .def("state", &smtk::task::TrivialProducerAgent::state) + .def("configure", [](smtk::task::TrivialProducerAgent& self, const std::string& jsonConfig) + { + auto config = nlohmann::json::parse(jsonConfig); + self.configure(config); + }) + .def("configuration", &smtk::task::TrivialProducerAgent::configuration) + .def("name", &smtk::task::TrivialProducerAgent::name) + .def("portData", [](smtk::task::TrivialProducerAgent& self, smtk::task::Port::Ptr port) + { + return self.portData(port.get()); + }, py::arg("port")) + .def("parent", &smtk::task::TrivialProducerAgent::parent, py::return_value_policy::reference_internal) + ; + return instance; +} + +#endif -- GitLab From 51a6ac95cc79888aa781a290580ac32fcbcb2ea4 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 20 Mar 2025 17:17:18 -0400 Subject: [PATCH 15/41] Expose `smtk::markup::Resource::lengthUnit` in python. --- smtk/markup/pybind11/PybindResource.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smtk/markup/pybind11/PybindResource.h b/smtk/markup/pybind11/PybindResource.h index 9e1a119986..3ee7ded19a 100644 --- a/smtk/markup/pybind11/PybindResource.h +++ b/smtk/markup/pybind11/PybindResource.h @@ -27,6 +27,8 @@ inline PySharedPtrClass< smtk::markup::Resource> pybind11_init_smtk_markup_Resou instance .def_static("create", []() { return smtk::markup::Resource::create(); }) .def("domains", [](smtk::markup::Resource& rr) { return rr.domains(); }) + .def("lengthUnit", &smtk::markup::Resource::lengthUnit) + .def("setLengthUnit", &smtk::markup::Resource::setLengthUnit, py::arg("lengthUnit")) ; return instance; } -- GitLab From 88999299ce908602d3e2d19e3a3afe168db865ee Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 20 Mar 2025 18:35:40 -0400 Subject: [PATCH 16/41] Provide overrides for `DiscreteGeometry::shape()`. --- smtk/markup/ImageData.h | 8 ++++++++ smtk/markup/UnstructuredData.h | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/smtk/markup/ImageData.h b/smtk/markup/ImageData.h index 96469de7fa..4e5e9413e9 100644 --- a/smtk/markup/ImageData.h +++ b/smtk/markup/ImageData.h @@ -79,7 +79,15 @@ public: /// Do not call this method outside of an operation and be aware /// that it _may_ modify m_pointIds and m_cellIds as well. bool setShapeData(vtkSmartPointer image, Superclass::ShapeOptions& options); + /// Return the geometric content for this node as a vtkImageData. + /// + /// This method is not inherited and provides output in the node's native format. vtkSmartPointer shapeData() const { return m_image; } + /// Return the geometric content for this node. + /// + /// This method is inherited from DiscreteGeometry and does not make any assumptions + /// about the type of data returned. + vtkSmartPointer shape() const override { return m_image; } /// Assign this node's state from \a source. bool assign(const smtk::graph::Component::ConstPtr& source, smtk::resource::CopyOptions& options) diff --git a/smtk/markup/UnstructuredData.h b/smtk/markup/UnstructuredData.h index 5b3c60c86d..e3594bef8e 100644 --- a/smtk/markup/UnstructuredData.h +++ b/smtk/markup/UnstructuredData.h @@ -75,7 +75,16 @@ public: /// Assign the \a mesh data as this object's shape and update Field children to match. bool setShapeData(vtkSmartPointer mesh, ShapeOptions& options); + /// Return the geometric content for this node as a vtkDataObject. + /// + /// Because unstructured data may be modeled with vtkPolyData, vtkUnstructuredGrid, + /// or vtkCellGrid data, there is no better type to return. vtkSmartPointer shapeData() const; + /// Return the geometric content for this node. + /// + /// This method is inherited from DiscreteGeometry and does not make any assumptions + /// about the type of data returned. + vtkSmartPointer shape() const override { return m_mesh; } const AssignedIds& pointIds() const { return *m_pointIds; } const AssignedIds& cellIds() const { return *m_cellIds; } -- GitLab From 1e4bdd0b712f326f43b741ced2dfd74bf4a0eed5 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 20 Mar 2025 18:36:10 -0400 Subject: [PATCH 17/41] An operation to scale markup geometry to match default units. --- smtk/markup/CMakeLists.txt | 1 + smtk/markup/Registrar.cxx | 2 + smtk/markup/operators/ChangeUnits.cxx | 168 ++++++++++++++++++++++++++ smtk/markup/operators/ChangeUnits.h | 54 +++++++++ smtk/markup/operators/ChangeUnits.sbt | 30 +++++ 5 files changed, 255 insertions(+) create mode 100644 smtk/markup/operators/ChangeUnits.cxx create mode 100644 smtk/markup/operators/ChangeUnits.h create mode 100644 smtk/markup/operators/ChangeUnits.sbt diff --git a/smtk/markup/CMakeLists.txt b/smtk/markup/CMakeLists.txt index 8e0a735ea7..d3ee3570c0 100644 --- a/smtk/markup/CMakeLists.txt +++ b/smtk/markup/CMakeLists.txt @@ -10,6 +10,7 @@ set(headers testing/cxx/helpers.h ) set(operations + ChangeUnits Create CreateArc CreateAnalyticShape diff --git a/smtk/markup/Registrar.cxx b/smtk/markup/Registrar.cxx index 47c90a2724..dd64223627 100644 --- a/smtk/markup/Registrar.cxx +++ b/smtk/markup/Registrar.cxx @@ -11,6 +11,7 @@ //============================================================================= #include "smtk/markup/Registrar.h" +#include "smtk/markup/operators/ChangeUnits.h" #include "smtk/markup/operators/Create.h" #include "smtk/markup/operators/CreateAnalyticShape.h" #include "smtk/markup/operators/CreateArc.h" @@ -48,6 +49,7 @@ namespace markup namespace { using OperationList = std::tuple< + ChangeUnits, Create, CreateArc, CreateAnalyticShape, diff --git a/smtk/markup/operators/ChangeUnits.cxx b/smtk/markup/operators/ChangeUnits.cxx new file mode 100644 index 0000000000..f2553140d4 --- /dev/null +++ b/smtk/markup/operators/ChangeUnits.cxx @@ -0,0 +1,168 @@ +//========================================================================= +// 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/markup/operators/ChangeUnits.h" + +#include "smtk/markup/DiscreteGeometry.h" + +#include "smtk/operation/MarkGeometry.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ReferenceItem.h" +#include "smtk/attribute/StringItem.h" + +#include "vtkImageData.h" +#include "vtkPointSet.h" +#include "vtkPoints.h" +#include "vtkSMPTools.h" +#include "vtkVector.h" + +#include "units/Converter.h" +#include "units/System.h" +#include "units/Unit.h" + +#include "smtk/markup/operators/ChangeUnits_xml.h" + +namespace smtk +{ +namespace markup +{ + +bool ChangeUnits::ableToOperate() +{ + if (!this->Superclass::ableToOperate()) + { + return false; + } + + std::string srcLengthUnitStr = this->parameters()->findString("source units")->value(); + + auto assocs = this->parameters()->associations(); + bool ok = assocs->numberOfValues() > 0; + for (const auto& assoc : *assocs) + { + auto* rsrc = dynamic_cast(assoc->parentResource()); + auto usys = rsrc ? rsrc->unitSystem() : nullptr; + // Every object associated must live in a resource with a unit system and default length unit + // that can be converted to the length unit for this operation. + if (!usys || rsrc->lengthUnit().empty()) + { + ok = false; + break; + } + + auto srcUnit = usys->unit(srcLengthUnitStr, &ok); + if (!ok) + { + break; + } + auto dstUnit = usys->unit(rsrc->lengthUnit(), &ok); + if (!ok) + { + break; + } + auto cvt = usys->convert(srcUnit, dstUnit); + if (!cvt) + { + ok = false; + break; + } + } + return ok; +} + +ChangeUnits::Result ChangeUnits::operateInternal() +{ + std::string srcLengthUnitStr = this->parameters()->findString("source units")->value(); + auto assocs = this->parameters()->associations(); + auto result = this->createResult(ChangeUnits::Outcome::SUCCEEDED); + auto mod = result->findComponent("modified"); + mod->setNumberOfValues(assocs->numberOfValues()); + for (const auto& assoc : *assocs) + { + auto* rsrc = dynamic_cast(assoc->parentResource()); + auto usys = rsrc ? rsrc->unitSystem() : nullptr; + if (!usys) + { + continue; + } + bool ok; + auto srcUnit = usys->unit(srcLengthUnitStr, &ok); + if (!ok) + { + continue; + } + auto dstUnit = usys->unit(rsrc->lengthUnit(), &ok); + if (!ok) + { + continue; + } + auto cvt = usys->convert(srcUnit, dstUnit); + auto spatialData = std::dynamic_pointer_cast(assoc); + auto mesh = spatialData->shape(); + if (!cvt || !mesh) + { + ok = false; + break; + } + if (auto* image = vtkImageData::SafeDownCast(mesh)) + { + vtkVector3d xx; + vtkVector3d yy; + image->GetOrigin(xx.GetData()); + image->GetSpacing(yy.GetData()); + for (int ii = 0; ii < 3; ++ii) + { + xx[ii] = cvt->transform(xx[ii]); + yy[ii] = cvt->transform(yy[ii]); + } + image->SetOrigin(xx.GetData()); + image->SetSpacing(yy.GetData()); + mesh->Modified(); + smtk::operation::MarkGeometry().markModified(assoc); + } + else if (auto* pset = vtkPointSet::SafeDownCast(mesh)) + { + auto* pts = pset->GetPoints(); + vtkSMPTools::For(0, pset->GetNumberOfPoints(), [&](vtkIdType begin, vtkIdType end) { + vtkVector3d xx; + for (vtkIdType pp = begin; pp < end; ++pp) + { + pts->GetPoint(pp, xx.GetData()); + for (int ii = 0; ii < 3; ++ii) + { + xx[ii] = cvt->transform(xx[ii]); + } + pts->SetPoint(pp, xx.GetData()); + } + }); + mesh->Modified(); + smtk::operation::MarkGeometry().markModified(spatialData); + } + else + { + smtkErrorMacro( + this->log(), + "Unhandled shape type '" << mesh->GetClassName() + << "' for " + "component '" + << assoc->name() << "' (" << assoc->typeName() << ")."); + } + } + return result; +} + +const char* ChangeUnits::xmlDescription() const +{ + return ChangeUnits_xml; +} + +} // namespace markup +} // namespace smtk diff --git a/smtk/markup/operators/ChangeUnits.h b/smtk/markup/operators/ChangeUnits.h new file mode 100644 index 0000000000..ff909c9b76 --- /dev/null +++ b/smtk/markup/operators/ChangeUnits.h @@ -0,0 +1,54 @@ +//========================================================================= +// 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_markup_ChangeUnits_h +#define smtk_markup_ChangeUnits_h + +#include "smtk/markup/Resource.h" + +#include "smtk/operation/XMLOperation.h" + +namespace smtk +{ +namespace markup +{ + +/**\brief Scale geometric data of associated objects from one length unit to another. + * + * If the destination unit is empty (the default) and the resource has a + * unit system with an active context (that specifies default units for + * each dimension), then the default length unit will be the destination. + * + * If the input association is spatial data, then the input nodes are + * blanked and output versions are created with the transformed geometry. + * + * If the input association is a markup resource, this changes the + * default length unit for the resource (and rescales all of its spatial + * data components if the default length unit was different but valid; + * no rescaling is performed if there was no prior default length unit). + */ +class SMTKMARKUP_EXPORT ChangeUnits : public smtk::operation::XMLOperation +{ +public: + smtkTypeMacro(smtk::markup::ChangeUnits); + smtkCreateMacro(ChangeUnits); + smtkSharedFromThisMacro(smtk::operation::Operation); + smtkSuperclassMacro(smtk::operation::XMLOperation); + + bool ableToOperate() override; + +protected: + Result operateInternal() override; + const char* xmlDescription() const override; +}; + +} // namespace markup +} // namespace smtk + +#endif // smtk_markup_ChangeUnits_h diff --git a/smtk/markup/operators/ChangeUnits.sbt b/smtk/markup/operators/ChangeUnits.sbt new file mode 100644 index 0000000000..20be19b824 --- /dev/null +++ b/smtk/markup/operators/ChangeUnits.sbt @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + The length units the geometry is specified in; the geometry + + + The length units the geometry is specified in; the geometry + will be converted from this length unit to the resource's default length units. + Examples include "mm", "meter", "inch", "foot", "yard", or even "furlong". + + + + + + + + + -- GitLab From c68543fd388896db2d032d8b905758f50e94000c Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 25 Mar 2025 04:13:48 -0400 Subject: [PATCH 18/41] Add `required-counts` to trivial-producer agent. This allows tasks to be incomplete if an operation has not added objects to the producer. --- doc/userguide/task/agents.rst | 4 + smtk/task/TrivialProducerAgent.cxx | 118 +++++++++++++++++++++++++++-- smtk/task/TrivialProducerAgent.h | 15 +++- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/doc/userguide/task/agents.rst b/doc/userguide/task/agents.rst index 0a97ef8267..7b5b7cb958 100644 --- a/doc/userguide/task/agents.rst +++ b/doc/userguide/task/agents.rst @@ -488,6 +488,10 @@ this agent: If an object is a resource, each specifier is a tuple holding a UUID and ``null``. If an object is a component, each specified is a tuple holding the UUID of the component's parent resource and the component's UUID. +* ``required-counts``: is a map from a role name to an array of 2 integers specifying + the minimum and maximum number of objects permitted in the given role. A ``-1`` for + the second array value indicates there is no maximum. If both numbers are ``-1``, + then no objects are allowed in the given role. Example """"""" diff --git a/smtk/task/TrivialProducerAgent.cxx b/smtk/task/TrivialProducerAgent.cxx index 8cba737887..821f1896eb 100644 --- a/smtk/task/TrivialProducerAgent.cxx +++ b/smtk/task/TrivialProducerAgent.cxx @@ -28,7 +28,7 @@ TrivialProducerAgent::TrivialProducerAgent(Task* owningTask) State TrivialProducerAgent::state() const { - return State::Completable; + return m_internalState; } void TrivialProducerAgent::configure(const Configuration& config) @@ -54,8 +54,8 @@ void TrivialProducerAgent::configure(const Configuration& config) for (const auto& objectSpec : entry.value()) { auto* obj = helper.objectFromJSONSpec(objectSpec, "port"); - std::cout << "Port \"" << m_name.data() << "\" add obj " << obj << " role " << entry.key() - << "\n"; + // std::cout << "Port \"" << m_name.data() << "\" add obj " << obj << " role " + // << entry.key() << "\n"; if (obj) { addedData |= m_data->addObject(obj, role); @@ -65,6 +65,29 @@ void TrivialProducerAgent::configure(const Configuration& config) } #endif } + it = config.find("required-counts"); + if (it != config.end()) + { + m_requiredObjectCounts = it->get>>(); + } + else + { + m_requiredObjectCounts.clear(); + } + it = config.find("internal-state"); + if (it != config.end()) + { + bool valid = false; + m_internalState = stateEnum(it->get(), &valid); + if (!valid) + { + m_internalState = State::Completable; + } + } + else + { + m_internalState = State::Completable; + } it = config.find("output-port"); if (it != config.end()) { @@ -83,6 +106,7 @@ void TrivialProducerAgent::configure(const Configuration& config) TrivialProducerAgent::Configuration TrivialProducerAgent::configuration() const { auto config = this->Superclass::configuration(); + config["internal-state"] = stateName(m_internalState); if (m_outputPort) { config["output-port"] = m_outputPort->name(); @@ -95,9 +119,27 @@ TrivialProducerAgent::Configuration TrivialProducerAgent::configuration() const { config["data"] = m_data; } + if (!m_requiredObjectCounts.empty()) + { + config["required-counts"] = m_requiredObjectCounts; + } return config; } +std::string TrivialProducerAgent::troubleshoot() const +{ + std::ostringstream msg; + switch (m_internalState) + { + default: + break; + case smtk::task::State::Incomplete: + msg << R"html(
  • Missing objects in roles.
  • )html"; + break; + } + return msg.str(); +} + std::shared_ptr TrivialProducerAgent::portData(const Port* port) const { if (port == m_outputPort) @@ -123,7 +165,14 @@ bool TrivialProducerAgent::addObjectInRole( { if (trivialProducer->name() == agentName) { - return trivialProducer->m_data->addObject(object, role); + State prev = trivialProducer->state(); + bool didAdd = trivialProducer->m_data->addObject(object, role); + if (didAdd) + { + trivialProducer->parent()->updateAgentState( + trivialProducer, prev, trivialProducer->computeInternalState()); + } + return didAdd; } } } @@ -146,7 +195,14 @@ bool TrivialProducerAgent::addObjectInRole( { if (trivialProducer->outputPort() == port) { - return trivialProducer->m_data->addObject(object, role); + State prev = trivialProducer->state(); + bool didAdd = trivialProducer->m_data->addObject(object, role); + if (didAdd) + { + trivialProducer->parent()->updateAgentState( + trivialProducer, prev, trivialProducer->computeInternalState()); + } + return didAdd; } } } @@ -170,8 +226,11 @@ bool TrivialProducerAgent::removeObjectFromRole( { if (trivialProducer->name() == agentName) { + State prev = trivialProducer->state(); if (trivialProducer->m_data->removeObject(object, role)) { + trivialProducer->parent()->updateAgentState( + trivialProducer, prev, trivialProducer->computeInternalState()); return true; } } @@ -197,8 +256,11 @@ bool TrivialProducerAgent::removeObjectFromRole( { if (trivialProducer->outputPort() == port) { + State prev = trivialProducer->state(); if (trivialProducer->m_data->removeObject(object, role)) { + trivialProducer->parent()->updateAgentState( + trivialProducer, prev, trivialProducer->computeInternalState()); return true; } } @@ -220,7 +282,13 @@ bool TrivialProducerAgent::resetData(Task* task, const std::string& agentName) { if (trivialProducer->name() == agentName) { + State prev = trivialProducer->state(); didChange |= trivialProducer->m_data->clear(); + if (didChange) + { + trivialProducer->parent()->updateAgentState( + trivialProducer, prev, trivialProducer->computeInternalState()); + } } } } @@ -238,11 +306,51 @@ bool TrivialProducerAgent::resetData(Task* task, Port* port) { if (auto* trivialProducer = dynamic_cast(agent)) { + State prev = trivialProducer->state(); didChange |= trivialProducer->m_data->clear(); + if (didChange) + { + trivialProducer->parent()->updateAgentState( + trivialProducer, prev, trivialProducer->computeInternalState()); + } } } return didChange; } +State TrivialProducerAgent::computeInternalState() +{ + State result = State::Completable; + for (const auto& entry : m_requiredObjectCounts) + { + auto it = m_data->data().find(entry.first); + if (it == m_data->data().end()) + { + result = State::Incomplete; + break; + } + auto numObj = static_cast(it->second.size()); + if (numObj >= entry.second.first && numObj <= entry.second.second) + { + // We are in range, skip following checks. + continue; + } + if (entry.second.second < 0 && numObj >= entry.second.first) + { + // We are allowed to have any number as long as we exceed the minimum. + continue; + } + if (entry.second.first < 0 && entry.second.second < 0 && numObj == 0) + { + // We are required to have 0 objects in this role. + continue; + } + result = State::Incomplete; + break; + } + m_internalState = result; + return result; +} + } // namespace task } // namespace smtk diff --git a/smtk/task/TrivialProducerAgent.h b/smtk/task/TrivialProducerAgent.h index 5367788021..776659a103 100644 --- a/smtk/task/TrivialProducerAgent.h +++ b/smtk/task/TrivialProducerAgent.h @@ -25,6 +25,9 @@ class ObjectsInRoles; /// assign objects to a task. The downstream or child tasks of this /// agent's task will then be configured with the objects in the roles /// as configured. +/// +/// Unless the task's configuration includes a minimum/maximum count +/// of objects per role, the task will always be completable. class SMTKCORE_EXPORT TrivialProducerAgent : public Agent { public: @@ -38,7 +41,10 @@ public: ///\brief Return the current state of the agent. /// - /// This agent will always be completable, even if no resources are assigned. + /// By default, this agent will always be completable, even if no resources are assigned. + /// However, if the agent's configuration contains minimum/maximum counts + /// for objects by role, the state will only be completable when the number of objects + /// in each specified role is in the allowed range. State state() const override; ///\brief Configure the agent based on a provided JSON configuration. @@ -47,6 +53,9 @@ public: ///\brief Produce a JSON configuration object for the current task state. Configuration configuration() const override; + ///\brief Provide feedback to users on how to make this agent completable. + std::string troubleshoot() const override; + ///\brief Return the port data from the agent. std::shared_ptr portData(const Port* port) const override; @@ -94,7 +103,11 @@ public: static bool resetData(Task* task, Port* port); protected: + virtual State computeInternalState(); + + State m_internalState{ State::Completable }; std::shared_ptr m_data; + std::map> m_requiredObjectCounts; Port* m_outputPort{ nullptr }; }; -- GitLab From e2194fd20e45a3b78c92ed88ad0bae41a9363b69 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 25 Mar 2025 04:17:11 -0400 Subject: [PATCH 19/41] Implement an operation-control button for the task-control view. This will run an operation when the user clicks the button. No checking is done to enable/disable the button based on available parameters. --- .../paraview/project/pqTaskControlView.cxx | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/smtk/extension/paraview/project/pqTaskControlView.cxx b/smtk/extension/paraview/project/pqTaskControlView.cxx index ea4f2d1a18..21a293bfe8 100644 --- a/smtk/extension/paraview/project/pqTaskControlView.cxx +++ b/smtk/extension/paraview/project/pqTaskControlView.cxx @@ -20,6 +20,7 @@ #include "smtk/view/Configuration.h" #include "smtk/view/Manager.h" +#include "smtk/operation/Manager.h" #include "smtk/project/Manager.h" #include "smtk/task/Active.h" #include "smtk/task/Manager.h" @@ -225,6 +226,105 @@ public: (void)layout; (void)task; (void)spec; + std::string opType; + if (!spec.attribute("Type", opType) || opType.empty()) + { + smtkErrorMacro(smtk::io::Logger::instance(), "Operation type must be specified."); + return; + } + std::string opLabel; + spec.attribute("Label", opLabel); + if (opLabel.empty()) + { + opLabel = "Run " + opType; + } + auto taskMgr = task->manager(); + auto mgrs = taskMgr->managers(); + auto opMgr = mgrs ? mgrs->get() : nullptr; + auto op = opMgr ? opMgr->create(opType) : nullptr; + if (!op) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Could not create \"" << opType << "\" from mgr " << opMgr << "."); + return; + } + auto opButton = new QPushButton; + opButton->setText(QString::fromStdString(opLabel)); + QObject::connect(opButton, &QAbstractButton::clicked, [task, opMgr, op, spec](bool clicked) { + for (const auto& paramSpec : spec.children()) + { + if (paramSpec.name() == "Association") + { + if (!op->parameters()->associations()) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "No associations can be specified for operation."); + continue; + } + op->parameters()->associations()->reset(); + auto data = task->manager()->workflowObjects(paramSpec, task); + for (const auto& entry : data) + { + for (const auto& obj : entry.second) + { + if (obj) + { + op->parameters()->associations()->appendValue(obj->shared_from_this()); + } + else + { + op->parameters()->associations()->appendValue(entry.first->shared_from_this()); + } + } + } + } + else if (paramSpec.name() == "Reference") + { + std::string refPath; + smtk::attribute::ReferenceItem::Ptr refItem; + if ( + !paramSpec.attribute("Path", refPath) || + !(refItem = op->parameters()->itemAtPathAs(refPath))) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "No reference item at path '" << refPath << "'."); + continue; + } + refItem->reset(); + auto data = task->manager()->workflowObjects(paramSpec, task); + for (const auto& entry : data) + { + for (const auto& obj : entry.second) + { + if (obj) + { + refItem->appendValue(obj->shared_from_this()); + } + else + { + refItem->appendValue(entry.first->shared_from_this()); + } + } + } + } + else + { + smtkErrorMacro( + smtk::io::Logger::instance(), "Unhandled element '" << paramSpec.name() << "'."); + } + } + opMgr->launchers()(op); + }); + + if (auto* formLayout = dynamic_cast(layout)) + { + formLayout->addRow(QString::fromStdString(opLabel), opButton); + } + else + { + layout->addWidget(opButton); + } ++this->numChildren; } -- GitLab From 85f14e2a06111700bc8c40ff99e9b290e38664ad Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 25 Mar 2025 04:18:34 -0400 Subject: [PATCH 20/41] Fix completion button; add form-group to task-control view. + Monitor the state of the task and enable the button as needed. + Add a form-group "item" --- .../paraview/project/pqTaskControlView.cxx | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/smtk/extension/paraview/project/pqTaskControlView.cxx b/smtk/extension/paraview/project/pqTaskControlView.cxx index 21a293bfe8..b57184130f 100644 --- a/smtk/extension/paraview/project/pqTaskControlView.cxx +++ b/smtk/extension/paraview/project/pqTaskControlView.cxx @@ -147,6 +147,15 @@ public: ctrl->setEnabled(task ? task->state() >= smtk::task::State::Completable : false); QObject::connect( ctrl, &QAbstractButton::toggled, m_view, &pqTaskControlView::updateTaskCompletion); + if (task) + { + m_taskStateObserver = task->observers().insert( + [ctrl, this](smtk::task::Task& t, smtk::task::State prev, smtk::task::State next) { + (void)prev; + ctrl->setChecked(t.isCompleted()); + ctrl->setEnabled(next >= smtk::task::State::Completable); + }); + } formLayout->addRow(label, ctrl); ++this->numChildren; } @@ -252,6 +261,7 @@ public: auto opButton = new QPushButton; opButton->setText(QString::fromStdString(opLabel)); QObject::connect(opButton, &QAbstractButton::clicked, [task, opMgr, op, spec](bool clicked) { + (void)clicked; for (const auto& paramSpec : spec.children()) { if (paramSpec.name() == "Association") @@ -328,6 +338,29 @@ public: ++this->numChildren; } + void addFormGroup( + QFormLayout* formLayout, + smtk::task::Task* task, + const smtk::view::Configuration::Component& spec, + QWidget*& parent, + int& childNum) + { + // Create an internal HBoxLayout and add children (presumed + // to be non-form controls) to that. Then add the HBox plus + // the form-title to \a formLayout. + QLayout* hbox = new QHBoxLayout; + for (const auto& formChild : spec.children()) + { + this->addChildItem(formChild, hbox, parent, childNum, task); + } + std::string formLabel; + if (!spec.attribute("Title", formLabel)) + { + formLabel = " "; + } + formLayout->addRow(QString::fromStdString(formLabel), hbox); + } + pqTaskControlView* m_view{ nullptr }; smtk::task::Manager* m_currentTaskManager{ nullptr }; smtk::task::Task* m_currentTask{ nullptr }; @@ -336,6 +369,7 @@ public: std::unordered_map m_representationSpecs; int numChildren{ 0 }; + smtk::task::Task::Observers::Key m_taskStateObserver; }; bool pqTaskControlView::Internal::addChildItem( @@ -348,7 +382,8 @@ bool pqTaskControlView::Internal::addChildItem( bool needLayout = !layout; smtk::string::Token childType = childSpec.name(); static std::unordered_set formItems{ "ActiveTaskStatus"_token, - "RepresentationControl"_token }; + "RepresentationControl"_token, + "FormGroup"_token }; bool isFormItem = (formItems.find(childType) != formItems.end()); auto* formLayout = dynamic_cast(layout); @@ -384,6 +419,9 @@ bool pqTaskControlView::Internal::addChildItem( case "OperationControl"_hash: this->addOperationControl(layout, task, childSpec); break; + case "FormGroup"_hash: + this->addFormGroup(formLayout, task, childSpec, parent, childNum); + break; default: return false; break; -- GitLab From 9e3d1cb7e8bf43d9e8a834dadc615bfd083a96c6 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 25 Mar 2025 04:19:27 -0400 Subject: [PATCH 21/41] =?UTF-8?q?Fix=20an=20error=20in=20translating=20XML?= =?UTF-8?q?=20to=20JSON=20specifications=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … for workflow-object computation. The "filter" parameter was misnamed and placed at the wrong level in the JSON hierarchy. --- smtk/task/Manager.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smtk/task/Manager.cxx b/smtk/task/Manager.cxx index aab352568f..9d23ca96bc 100644 --- a/smtk/task/Manager.cxx +++ b/smtk/task/Manager.cxx @@ -405,7 +405,7 @@ Manager::ResourceObjectMap Manager::workflowObjects(const nlohmann::json& spec, { resource = comp->parentResource(); } - if (!resource || !this->isResourceRelevant(resource, filter)) + if (!resource) { continue; } @@ -445,7 +445,7 @@ Manager::ResourceObjectMap Manager::workflowObjects( auto filters = filtersForSpec(entry); if (!filters.empty()) { - sourceSpec["filters"] = filters; + jsonSpec["filter"] = filters; } jsonSpec["source"] = sourceSpec; } @@ -456,7 +456,7 @@ Manager::ResourceObjectMap Manager::workflowObjects( auto filters = filtersForSpec(entry); if (!filters.empty()) { - sourceSpec["filters"] = filters; + jsonSpec["filter"] = filters; } jsonSpec["source"] = sourceSpec; } -- GitLab From 9a5f058544c7ae00ef64379763ca6a2fac09bd9b Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 25 Mar 2025 13:00:13 -0400 Subject: [PATCH 22/41] Python utility for unit conversion using a resource's unit system. --- smtk/resource/pybind11/PybindResource.h | 36 +++++++++++++++++++ smtk/resource/testing/python/CMakeLists.txt | 1 + .../testing/python/testResourceUnitSystem.py | 35 ++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 smtk/resource/testing/python/testResourceUnitSystem.py diff --git a/smtk/resource/pybind11/PybindResource.h b/smtk/resource/pybind11/PybindResource.h index 8812f707d6..9ff47c70bc 100644 --- a/smtk/resource/pybind11/PybindResource.h +++ b/smtk/resource/pybind11/PybindResource.h @@ -23,6 +23,11 @@ #include "smtk/resource/pybind11/PyResource.h" +// For unit conversion +#include "units/Measurement.h" +#include "units/System.h" +#include "units/Unit.h" + namespace py = pybind11; inline PySharedPtrClass< smtk::resource::Resource, smtk::resource::PyResource, smtk::resource::PersistentObject > pybind11_init_smtk_resource_Resource(py::module &m) @@ -150,6 +155,37 @@ inline PySharedPtrClass< smtk::resource::Resource, smtk::resource::PyResource, s .def("setMarkedForRemoval", &smtk::resource::Resource::setMarkedForRemoval, py::arg("val")) .def("setName", &smtk::resource::Resource::setName, py::arg("name")) .def("typeName", &smtk::resource::Resource::typeName) + .def("createDefaultUnitSystem", [](smtk::resource::Resource* rsrc) + { + auto sys = rsrc->unitSystem(); + if (!sys) + { + sys = units::System::createWithDefaults(); + rsrc->setUnitSystem(sys); + return true; + } + return false; + } + ) + .def("unitConversion", [](smtk::resource::Resource* rsrc, const std::string& measurement, const std::string& unit) -> double + { + double result = std::numeric_limits::quiet_NaN(); + auto sys = rsrc->unitSystem(); + if (!sys) + { + return result; + } + bool didConvert; + auto valueIn = sys->measurement(measurement, &didConvert); + if (!didConvert) { return result; } + auto unitOut = sys->unit(unit, &didConvert); + if (!didConvert) { return result; } + auto valueOut = sys->convert(valueIn, unitOut, &didConvert); + if (!didConvert) { return result; } + result = valueOut.m_value; + return result; + }, py::arg("valueWithUnitsIn"), py::arg("unitsOut") + ) .def("visit", &smtk::resource::Resource::visit, py::arg("v")) .def_static("visuallyLinkedRole", &smtk::resource::Resource::visuallyLinkedRole) .def_readonly_static("VisuallyLinkedRole", &smtk::resource::Resource::VisuallyLinkedRole) diff --git a/smtk/resource/testing/python/CMakeLists.txt b/smtk/resource/testing/python/CMakeLists.txt index a3cff4a0a2..1a22079cb2 100644 --- a/smtk/resource/testing/python/CMakeLists.txt +++ b/smtk/resource/testing/python/CMakeLists.txt @@ -1,5 +1,6 @@ set(smtkResourcePythonTests testResource + testResourceUnitSystem ) # Additional tests that require SMTK_DATA_DIR diff --git a/smtk/resource/testing/python/testResourceUnitSystem.py b/smtk/resource/testing/python/testResourceUnitSystem.py new file mode 100644 index 0000000000..13103197f6 --- /dev/null +++ b/smtk/resource/testing/python/testResourceUnitSystem.py @@ -0,0 +1,35 @@ +# ============================================================================= +# +# 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.common +import smtk.resource +import smtk.model + +import math + +# Create a resource; by default it has no unit system. +rsrc = smtk.model.Resource.create() +# Test that conversion fails with no unit system. +result = rsrc.unitConversion("1 ft", "m") +if not math.isnan(result): + raise RuntimeError('Unit conversion with no unit system should fail.') + +rsrc.createDefaultUnitSystem() +result = rsrc.unitConversion("1 ft", "m") +if math.fabs(result - 0.3048) > 1e-7: + raise RuntimeError('Unit conversion failed.') + +result = rsrc.unitConversion("1 ft", "second") +if not math.isnan(result): + raise RuntimeError( + 'Unit conversion between incompatible units should fail.') -- GitLab From 16ffb517cb44cb41e8bc757283d5952907bf6c18 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sat, 22 Mar 2025 13:40:17 -0400 Subject: [PATCH 23/41] Add a disk widget. --- data/attribute/widgets/gallery-disk.sbt | 65 ++ smtk/extension/paraview/server/smconfig.xml | 131 +++ .../extension/paraview/widgets/CMakeLists.txt | 3 + .../widgets/plugin/pqSMTKWidgetsAutoStart.cxx | 22 +- .../paraview/widgets/pqDiskPropertyWidget.cxx | 138 +++ .../paraview/widgets/pqDiskPropertyWidget.h | 45 + .../paraview/widgets/pqSMTKDiskItemWidget.cxx | 230 ++++ .../paraview/widgets/pqSMTKDiskItemWidget.h | 88 ++ .../widgets/resources/pqDiskPropertyWidget.ui | 130 +++ smtk/extension/vtk/source/CMakeLists.txt | 2 + smtk/extension/vtk/source/vtkDisk.cxx | 185 +++ smtk/extension/vtk/source/vtkDisk.h | 121 ++ smtk/extension/vtk/source/vtkImplicitDisk.cxx | 113 ++ smtk/extension/vtk/source/vtkImplicitDisk.h | 121 ++ smtk/extension/vtk/widgets/CMakeLists.txt | 2 + .../vtk/widgets/vtkDiskRepresentation.cxx | 1030 +++++++++++++++++ .../vtk/widgets/vtkDiskRepresentation.h | 383 ++++++ smtk/extension/vtk/widgets/vtkDiskWidget.cxx | 428 +++++++ smtk/extension/vtk/widgets/vtkDiskWidget.h | 162 +++ 19 files changed, 3387 insertions(+), 12 deletions(-) create mode 100644 data/attribute/widgets/gallery-disk.sbt create mode 100644 smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx create mode 100644 smtk/extension/paraview/widgets/pqDiskPropertyWidget.h create mode 100644 smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx create mode 100644 smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.h create mode 100644 smtk/extension/paraview/widgets/resources/pqDiskPropertyWidget.ui create mode 100644 smtk/extension/vtk/source/vtkDisk.cxx create mode 100644 smtk/extension/vtk/source/vtkDisk.h create mode 100644 smtk/extension/vtk/source/vtkImplicitDisk.cxx create mode 100644 smtk/extension/vtk/source/vtkImplicitDisk.h create mode 100644 smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx create mode 100644 smtk/extension/vtk/widgets/vtkDiskRepresentation.h create mode 100644 smtk/extension/vtk/widgets/vtkDiskWidget.cxx create mode 100644 smtk/extension/vtk/widgets/vtkDiskWidget.h diff --git a/data/attribute/widgets/gallery-disk.sbt b/data/attribute/widgets/gallery-disk.sbt new file mode 100644 index 0000000000..8853ddbf05 --- /dev/null +++ b/data/attribute/widgets/gallery-disk.sbt @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + 0.0 + 0.0 + 0.0 + + + + + 0.0 + 0.0 + 1.0 + + + + + 0.5 + + + + + + + + + + + + + + + + + + + + diff --git a/smtk/extension/paraview/server/smconfig.xml b/smtk/extension/paraview/server/smconfig.xml index 7a15c29fec..597a266e5a 100644 --- a/smtk/extension/paraview/server/smconfig.xml +++ b/smtk/extension/paraview/server/smconfig.xml @@ -1171,6 +1171,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1183,6 +1240,13 @@ name="ConeWidget"> + + + @@ -1228,6 +1292,38 @@ + + + + + + + + + + + + + + + + + @@ -1975,6 +2071,41 @@ + + + + + Enable/Disable widget interaction. + + + + + + + + + + + + + + + + + + diff --git a/smtk/extension/paraview/widgets/CMakeLists.txt b/smtk/extension/paraview/widgets/CMakeLists.txt index d1bfb909d6..24a19f19c2 100644 --- a/smtk/extension/paraview/widgets/CMakeLists.txt +++ b/smtk/extension/paraview/widgets/CMakeLists.txt @@ -8,11 +8,13 @@ set(headers set(classes Registrar pqConePropertyWidget + pqDiskPropertyWidget pqPointPropertyWidget pqSMTKAttributeItemWidget pqSMTKBoxItemWidget pqSMTKTransformWidget pqSMTKConeItemWidget + pqSMTKDiskItemWidget pqSMTKInfiniteCylinderItemWidget pqSMTKLineItemWidget pqSMTKPointItemWidget @@ -23,6 +25,7 @@ set(classes set(ui_files resources/pqConePropertyWidget.ui + resources/pqDiskPropertyWidget.ui resources/pqPointPropertyWidget.ui ) diff --git a/smtk/extension/paraview/widgets/plugin/pqSMTKWidgetsAutoStart.cxx b/smtk/extension/paraview/widgets/plugin/pqSMTKWidgetsAutoStart.cxx index 07621f3570..ebb52efac2 100644 --- a/smtk/extension/paraview/widgets/plugin/pqSMTKWidgetsAutoStart.cxx +++ b/smtk/extension/paraview/widgets/plugin/pqSMTKWidgetsAutoStart.cxx @@ -14,6 +14,7 @@ #include "smtk/extension/paraview/widgets/pqSMTKBoxItemWidget.h" #include "smtk/extension/paraview/widgets/pqSMTKConeItemWidget.h" #include "smtk/extension/paraview/widgets/pqSMTKCoordinateFrameItemWidget.h" +#include "smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.h" #include "smtk/extension/paraview/widgets/pqSMTKInfiniteCylinderItemWidget.h" #include "smtk/extension/paraview/widgets/pqSMTKLineItemWidget.h" #include "smtk/extension/paraview/widgets/pqSMTKPlaneItemWidget.h" @@ -46,24 +47,21 @@ void pqSMTKWidgetsAutoStart::startup() */ // Register qtItem widget subclasses implemented using ParaView 3-D widgets: + // clang-format off qtSMTKUtilities::registerItemConstructor("Box", pqSMTKBoxItemWidget::createBoxItemWidget); qtSMTKUtilities::registerItemConstructor("Cone", pqSMTKConeItemWidget::createConeItemWidget); - qtSMTKUtilities::registerItemConstructor( - "Cylinder", pqSMTKConeItemWidget::createCylinderItemWidget); - qtSMTKUtilities::registerItemConstructor( - "CoordinateFrame", pqSMTKCoordinateFrameItemWidget::createCoordinateFrameItemWidget); - qtSMTKUtilities::registerItemConstructor( - "InfiniteCylinder", pqSMTKInfiniteCylinderItemWidget::createCylinderItemWidget); + qtSMTKUtilities::registerItemConstructor("Cylinder", pqSMTKConeItemWidget::createCylinderItemWidget); + qtSMTKUtilities::registerItemConstructor("CoordinateFrame", pqSMTKCoordinateFrameItemWidget::createCoordinateFrameItemWidget); + qtSMTKUtilities::registerItemConstructor("Disk", pqSMTKDiskItemWidget::createDiskItemWidget); + qtSMTKUtilities::registerItemConstructor("InfiniteCylinder", pqSMTKInfiniteCylinderItemWidget::createCylinderItemWidget); qtSMTKUtilities::registerItemConstructor("Line", pqSMTKLineItemWidget::createLineItemWidget); qtSMTKUtilities::registerItemConstructor("Plane", pqSMTKPlaneItemWidget::createPlaneItemWidget); qtSMTKUtilities::registerItemConstructor("Point", pqSMTKPointItemWidget::createPointItemWidget); qtSMTKUtilities::registerItemConstructor("Slice", pqSMTKSliceItemWidget::createSliceItemWidget); - qtSMTKUtilities::registerItemConstructor( - "Sphere", pqSMTKSphereItemWidget::createSphereItemWidget); - qtSMTKUtilities::registerItemConstructor( - "Spline", pqSMTKSplineItemWidget::createSplineItemWidget); - qtSMTKUtilities::registerItemConstructor( - "Transform", pqSMTKTransformWidget::createTransformWidget); + qtSMTKUtilities::registerItemConstructor("Sphere", pqSMTKSphereItemWidget::createSphereItemWidget); + qtSMTKUtilities::registerItemConstructor("Spline", pqSMTKSplineItemWidget::createSplineItemWidget); + qtSMTKUtilities::registerItemConstructor("Transform", pqSMTKTransformWidget::createTransformWidget); + // clang-format on } void pqSMTKWidgetsAutoStart::shutdown() diff --git a/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx b/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx new file mode 100644 index 0000000000..175a650fd2 --- /dev/null +++ b/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx @@ -0,0 +1,138 @@ +//========================================================================= +// 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/extension/paraview/widgets/pqDiskPropertyWidget.h" +#include "smtk/extension/paraview/widgets/pqPointPickingVisibilityHelper.h" +#include "smtk/extension/paraview/widgets/ui_pqDiskPropertyWidget.h" + +#include "pqCoreUtilities.h" +#include "pqPointPickingHelper.h" +#include "pqView.h" + +#include "vtkSMNewWidgetRepresentationProxy.h" +#include "vtkSMProperty.h" +#include "vtkSMPropertyGroup.h" +#include "vtkSMPropertyHelper.h" + +#include "vtkCommand.h" +#include "vtkMath.h" +#include "vtkVector.h" +#include "vtkVectorOperators.h" + +#include + +class pqDiskPropertyWidget::Internals +{ +public: + Internals() = default; + + Ui::DiskPropertyWidget Ui; +}; + +pqDiskPropertyWidget::pqDiskPropertyWidget( + vtkSMProxy* smproxy, + vtkSMPropertyGroup* smgroup, + QWidget* parentObj) + : Superclass("representations", "DiskWidgetRepresentation", smproxy, smgroup, parentObj) + , m_p(new pqDiskPropertyWidget::Internals()) +{ + Ui::DiskPropertyWidget& ui = m_p->Ui; + ui.setupUi(this); + + // link show3DWidget checkbox + QObject::connect( + ui.show3DWidget, &QCheckBox::toggled, this, &pqDiskPropertyWidget::setWidgetVisible); + QObject::connect( + this, &pqDiskPropertyWidget::widgetVisibilityToggled, ui.show3DWidget, &QCheckBox::setChecked); + this->setWidgetVisible(ui.show3DWidget->isChecked()); + +#ifdef Q_OS_MAC + ui.pickLabel->setText(ui.pickLabel->text().replace("Ctrl", "Cmd")); +#endif + + if (vtkSMProperty* ctr = smgroup->GetProperty("CenterPoint")) + { + ui.labelCenter->setText(tr(ctr->GetXMLLabel())); + this->addPropertyLink(ui.centerX, "text2", SIGNAL(textChangedAndEditingFinished()), ctr, 0); + this->addPropertyLink(ui.centerY, "text2", SIGNAL(textChangedAndEditingFinished()), ctr, 1); + this->addPropertyLink(ui.centerZ, "text2", SIGNAL(textChangedAndEditingFinished()), ctr, 2); + ui.labelCenter->setText(ctr->GetXMLLabel()); + } + else + { + qCritical("Missing required property for function 'CenterPoint'."); + } + + if (vtkSMProperty* nrm = smgroup->GetProperty("Normal")) + { + ui.labelNormal->setText(tr(nrm->GetXMLLabel())); + this->addPropertyLink(ui.normalX, "text2", SIGNAL(textChangedAndEditingFinished()), nrm, 0); + this->addPropertyLink(ui.normalY, "text2", SIGNAL(textChangedAndEditingFinished()), nrm, 1); + this->addPropertyLink(ui.normalZ, "text2", SIGNAL(textChangedAndEditingFinished()), nrm, 2); + ui.labelNormal->setText(nrm->GetXMLLabel()); + } + else + { + qCritical("Missing required property for function 'Normal'."); + } + + if (vtkSMProperty* rad = smgroup->GetProperty("Radius")) + { + ui.labelRadius->setText(tr(rad->GetXMLLabel())); + this->addPropertyLink(ui.radius, "text2", SIGNAL(textChangedAndEditingFinished()), rad, 0); + } + else + { + qCritical("Missing required property for function 'Radius'."); + } + + pqPointPickingHelper* pickHelper = new pqPointPickingHelper(QKeySequence(tr("P")), false, this); + QObject::connect( + this, &pqDiskPropertyWidget::viewChanged, pickHelper, &pqPointPickingHelper::setView); + QObject::connect(pickHelper, &pqPointPickingHelper::pick, this, &pqDiskPropertyWidget::pick); + pqPointPickingVisibilityHelper{ *this, *pickHelper }; + + pqPointPickingHelper* pickHelper2 = + new pqPointPickingHelper(QKeySequence(tr("Ctrl+P")), true, this); + QObject::connect( + this, &pqDiskPropertyWidget::viewChanged, pickHelper2, &pqPointPickingHelper::setView); + QObject::connect(pickHelper2, &pqPointPickingHelper::pick, this, &pqDiskPropertyWidget::pick); + pqPointPickingVisibilityHelper{ *this, *pickHelper2 }; + + pqCoreUtilities::connect( + this->widgetProxy(), vtkCommand::PropertyModifiedEvent, this, SLOT(updateInformationLabels())); + this->updateInformationLabels(); +} + +pqDiskPropertyWidget::~pqDiskPropertyWidget() = default; + +void pqDiskPropertyWidget::pick(double wx, double wy, double wz) +{ + double position[3] = { wx, wy, wz }; + vtkSMNewWidgetRepresentationProxy* wdgProxy = this->widgetProxy(); + vtkSMPropertyHelper(wdgProxy, "CenterPoint").Set(position, 3); + wdgProxy->UpdateVTKObjects(); + Q_EMIT this->changeAvailable(); + this->render(); +} + +void pqDiskPropertyWidget::updateInformationLabels() +{ + // Ui::DiskPropertyWidget& ui = m_p->Ui; + + vtkVector3d ctr, nrm; + vtkSMProxy* wproxy = this->widgetProxy(); + vtkSMPropertyHelper(wproxy, "CenterPoint").Get(ctr.GetData(), 3); + vtkSMPropertyHelper(wproxy, "Normal").Get(nrm.GetData(), 3); +} + +void pqDiskPropertyWidget::placeWidget() +{ + // Nothing to do? +} diff --git a/smtk/extension/paraview/widgets/pqDiskPropertyWidget.h b/smtk/extension/paraview/widgets/pqDiskPropertyWidget.h new file mode 100644 index 0000000000..b5a5c2a89f --- /dev/null +++ b/smtk/extension/paraview/widgets/pqDiskPropertyWidget.h @@ -0,0 +1,45 @@ +//========================================================================= +// 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_extension_paraview_widgets_pqDiskPropertyWidget_h +#define smtk_extension_paraview_widgets_pqDiskPropertyWidget_h + +#include "smtk/extension/paraview/widgets/pqSMTKInteractivePropertyWidget.h" +#include "smtk/extension/paraview/widgets/smtkPQWidgetsExtModule.h" + +/**\brief Present a widget for placing a planar disc in a ParaView render-view. + * + * This widget allows users to choose the center point, normal, and radius of a + * single disc in either the active 3-d view or manually inside a property panel. + */ +class SMTKPQWIDGETSEXT_EXPORT pqDiskPropertyWidget : public pqSMTKInteractivePropertyWidget +{ + Q_OBJECT + using Superclass = pqSMTKInteractivePropertyWidget; + +public: + pqDiskPropertyWidget(vtkSMProxy* proxy, vtkSMPropertyGroup* smgroup, QWidget* parent = nullptr); + ~pqDiskPropertyWidget() override; + +public Q_SLOTS: + void pick(double, double, double); + +protected Q_SLOTS: + void updateInformationLabels(); + void placeWidget() override; + +protected: + class Internals; + Internals* m_p; + +private: + Q_DISABLE_COPY(pqDiskPropertyWidget); +}; + +#endif // smtk_extension_paraview_widgets_pqDiskPropertyWidget_h diff --git a/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx b/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx new file mode 100644 index 0000000000..f004ab4412 --- /dev/null +++ b/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx @@ -0,0 +1,230 @@ +//========================================================================= +// 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/extension/paraview/widgets/pqSMTKDiskItemWidget.h" +#include "smtk/extension/paraview/widgets/pqDiskPropertyWidget.h" +#include "smtk/extension/paraview/widgets/pqSMTKAttributeItemWidgetP.h" + +#include "smtk/attribute/DoubleItem.h" +#include "smtk/attribute/GroupItem.h" + +#include "smtk/io/Logger.h" + +#include "pqActiveObjects.h" +#include "pqApplicationCore.h" +#include "pqDataRepresentation.h" +#include "pqImplicitPlanePropertyWidget.h" +#include "pqObjectBuilder.h" +#include "pqPipelineSource.h" +#include "pqServer.h" +#include "pqSpherePropertyWidget.h" +#include "vtkMath.h" +#include "vtkPVXMLElement.h" +#include "vtkSMNewWidgetRepresentationProxy.h" +#include "vtkSMProperty.h" +#include "vtkSMPropertyGroup.h" +#include "vtkSMPropertyHelper.h" +#include "vtkSMProxy.h" +#include "vtkVector.h" +#include "vtkVectorOperators.h" + +using qtItem = smtk::extension::qtItem; +using qtAttributeItemInfo = smtk::extension::qtAttributeItemInfo; + +pqSMTKDiskItemWidget::pqSMTKDiskItemWidget( + const smtk::extension::qtAttributeItemInfo& info, + Qt::Orientation orient) + : pqSMTKAttributeItemWidget(info, orient) +{ + this->createWidget(); +} + +pqSMTKDiskItemWidget::~pqSMTKDiskItemWidget() = default; + +qtItem* pqSMTKDiskItemWidget::createDiskItemWidget(const qtAttributeItemInfo& info) +{ + return new pqSMTKDiskItemWidget(info); +} + +bool pqSMTKDiskItemWidget::createProxyAndWidget( + vtkSMProxy*& proxy, + pqInteractivePropertyWidget*& widget) +{ + ItemBindings binding; + std::vector items; + bool haveItems = this->fetchDiskItems(binding, items); + if (!haveItems || binding == ItemBindings::Invalid) + { + smtkErrorMacro(smtk::io::Logger::instance(), "Could not find items for widget."); + return false; + } + + // I. Create the ParaView widget and a proxy for its representation. + pqApplicationCore* paraViewApp = pqApplicationCore::instance(); + pqServer* server = paraViewApp->getActiveServer(); + pqObjectBuilder* builder = paraViewApp->getObjectBuilder(); + + proxy = builder->createProxy("implicit_functions", "ImplicitDisk", server, ""); + if (!proxy) + { + return false; + } + auto* diskWidget = new pqDiskPropertyWidget(proxy, proxy->GetPropertyGroup(0)); + widget = diskWidget; + + // II. Initialize the properties. + m_p->m_pvwidget = widget; + this->updateWidgetFromItem(); + auto* widgetProxy = widget->widgetProxy(); + widgetProxy->UpdateVTKObjects(); + // vtkSMPropertyHelper(widgetProxy, "RotationEnabled").Set(false); + + return widget != nullptr; +} + +bool pqSMTKDiskItemWidget::updateItemFromWidgetInternal() +{ + vtkSMNewWidgetRepresentationProxy* widget = m_p->m_pvwidget->widgetProxy(); + std::vector items; + ItemBindings binding; + if (!this->fetchDiskItems(binding, items)) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Item widget has an update but the item(s) do not exist or are not sized properly."); + return false; + } + + // Values held by widget + vtkVector3d ctr; + vtkVector3d nrm; + double rad; + vtkSMPropertyHelper ctrHelper(widget, "CenterPoint"); + vtkSMPropertyHelper nrmHelper(widget, "Normal"); + vtkSMPropertyHelper radHelper(widget, "Radius"); + ctrHelper.Get(ctr.GetData(), 3); + nrmHelper.Get(nrm.GetData(), 3); + radHelper.Get(&rad, 1); + bool didChange = false; + + // Current values held in items: + vtkVector3d curPt0; + vtkVector3d curPt1; + double curRad; + + // Translate widget values to item values and fetch current item values: + curPt0 = vtkVector3d(&(*items[0]->begin())); + curPt1 = vtkVector3d(&(*items[1]->begin())); + curRad = *items[2]->begin(); + switch (binding) + { + case ItemBindings::DiskPointsRadii: + if (curPt0 != ctr || curPt1 != nrm || curRad != rad) + { + didChange = true; + items[0]->setValues(ctr.GetData(), ctr.GetData() + 3); + items[1]->setValues(nrm.GetData(), nrm.GetData() + 3); + items[2]->setValue(rad); + } + break; + case ItemBindings::Invalid: + default: + smtkErrorMacro(smtk::io::Logger::instance(), "Unable to determine item binding."); + break; + } + + return didChange; +} + +bool pqSMTKDiskItemWidget::updateWidgetFromItemInternal() +{ + vtkSMNewWidgetRepresentationProxy* widget = m_p->m_pvwidget->widgetProxy(); + std::vector items; + ItemBindings binding; + if (!this->fetchDiskItems(binding, items)) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Item signaled an update but the item(s) do not exist or are not sized properly."); + return false; + } + + // Unlike updateItemFromWidget, we don't care if we cause ParaView an unnecessary update; + // we might cause an extra render but we won't accidentally mark a resource as modified. + // Since there's no need to compare new values to old, this is simpler than updateItemFromWidget: + vtkVector3d ctr(&(*items[0]->begin())); + vtkVector3d nrm(&(*items[1]->begin())); + double radius = items[2]->value(0); + vtkSMPropertyHelper(widget, "CenterPoint").Set(ctr.GetData(), 3); + vtkSMPropertyHelper(widget, "Normal").Set(nrm.GetData(), 3); + vtkSMPropertyHelper(widget, "Radius").Set(&radius, 1); + switch (binding) + { + case ItemBindings::DiskPointsRadii: + break; + case ItemBindings::Invalid: + default: + { + smtkErrorMacro(smtk::io::Logger::instance(), "Unhandled item binding."); + } + break; + } + return true; // TODO: determine whether values were changed to avoid unnecessary renders. +} + +bool pqSMTKDiskItemWidget::fetchDiskItems( + ItemBindings& binding, + std::vector& items) +{ + items.clear(); + + // Check to see if item is a group containing items of double-vector items. + auto groupItem = m_itemInfo.itemAs(); + if (!groupItem || groupItem->numberOfGroups() < 1 || groupItem->numberOfItemsPerGroup() < 3) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "Expected a group item with 1 group of 3 or more items."); + return false; + } + + // Find items in the group based on names in the configuration info: + // ctr, nrm, rad + std::string ctrItemName; + std::string nrmItemName; + std::string radItemName; + if (!m_itemInfo.component().attribute("Center", ctrItemName)) + { + ctrItemName = "Center"; + } + if (!m_itemInfo.component().attribute("Normal", nrmItemName)) + { + nrmItemName = "Normal"; + } + if (!m_itemInfo.component().attribute("Radius", radItemName)) + { + radItemName = "Radius"; + } + auto ctrItem = groupItem->findAs(ctrItemName); + auto nrmItem = groupItem->findAs(nrmItemName); + auto radItem = groupItem->findAs(radItemName); + + if ( + ctrItem && ctrItem->numberOfValues() == 3 && nrmItem && nrmItem->numberOfValues() == 3 && + radItem && radItem->numberOfValues() == 1) + { + items.push_back(ctrItem); + items.push_back(nrmItem); + items.push_back(radItem); + binding = ItemBindings::DiskPointsRadii; + return true; + } + + binding = ItemBindings::Invalid; + return false; +} diff --git a/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.h b/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.h new file mode 100644 index 0000000000..b411448a6e --- /dev/null +++ b/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.h @@ -0,0 +1,88 @@ +//========================================================================= +// 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_extension_paraview_widgets_pqSMTKDiskItemWidget_h +#define smtk_extension_paraview_widgets_pqSMTKDiskItemWidget_h + +#include "smtk/extension/paraview/widgets/pqSMTKAttributeItemWidget.h" + +/**\brief Display an interactive disk widget. + * + * The widget currently accepts a center point, normal, and radius + * that defines 2-d disk embedded in 3-d. These values should be + * child double-items of an SMTK group. Your view-configuration XML + * should mention this item explicitly and specify the path to the + * group as well as the names of individual child items like so: + * + * ```xml + * + * + * + * + * ``` + * + * In the future, other item types (such as a GroupItem holding + * children specifying 3 points on the circumference of a disk; + * or a center point and point on the circumferece; or a pair of + * diametrically opposed points and a normal) may be supported. + * + * Currently, there is no support to initialize the placement to + * a given set of bounds; if you use this widget as part of the + * user interface to an operation, implement a configure() method + * on the operation to contextually place the widget based on + * associations. + */ +class SMTKPQWIDGETSEXT_EXPORT pqSMTKDiskItemWidget : public pqSMTKAttributeItemWidget +{ + Q_OBJECT +public: + pqSMTKDiskItemWidget( + const smtk::extension::qtAttributeItemInfo& info, + Qt::Orientation orient = Qt::Horizontal); + ~pqSMTKDiskItemWidget() override; + + /// Create an instance of the widget that allows users to define a disk. + static qtItem* createDiskItemWidget(const qtAttributeItemInfo& info); + + bool createProxyAndWidget(vtkSMProxy*& proxy, pqInteractivePropertyWidget*& widget) override; + +protected Q_SLOTS: + /// Retrieve property values from ParaView proxy and store them in the attribute's Item. + bool updateItemFromWidgetInternal() override; + /// Retrieve property values from the attribute's Item and update the ParaView proxy. + bool updateWidgetFromItemInternal() override; + +protected: + /// Describe how an attribute's items specify a disk or cylinder. + enum class ItemBindings + { + /// 2 items with 3 values, 1 items with 1 value (cx, cy, cz, nx, ny, nz, rr). + DiskPointsRadii, + /// No consistent set of items detected. + Invalid + }; + /**\brief Starting with the widget's assigned item (which must currently + * be a GroupItem), determine and return bound items. + * + * The named item must be a Group holding items as called out by + * one of the valid ItemBindings enumerants. + * The items inside the Group must currently be Double items. + * + * If errors (such as a lack of matching item names or an + * unexpected number of values per item) are encountered, + * this method returns false. + */ + bool fetchDiskItems(ItemBindings& binding, std::vector& items); +}; + +#endif // smtk_extension_paraview_widgets_pqSMTKDiskItemWidget_h diff --git a/smtk/extension/paraview/widgets/resources/pqDiskPropertyWidget.ui b/smtk/extension/paraview/widgets/resources/pqDiskPropertyWidget.ui new file mode 100644 index 0000000000..3aeb2a730b --- /dev/null +++ b/smtk/extension/paraview/widgets/resources/pqDiskPropertyWidget.ui @@ -0,0 +1,130 @@ + + + DiskPropertyWidget + + + + 0 + 0 + 341 + 155 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + + + + Normal + + + true + + + + + + + + 75 + true + + + + Note: Use 'P' to move the center to the pointer or 'Ctrl+P' to snap to the closest mesh point. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Show disk + + + true + + + + + + + Radius + + + + + + + + + + + + + + + + Center + + + true + + + + + + + + + + + + + + + + + pqDoubleLineEdit + QLineEdit +
    pqDoubleLineEdit.h
    +
    +
    + + show3DWidget + centerX + centerY + centerZ + normalX + normalY + normalZ + radius + + + +
    diff --git a/smtk/extension/vtk/source/CMakeLists.txt b/smtk/extension/vtk/source/CMakeLists.txt index 1f7d2a9221..040407f99f 100644 --- a/smtk/extension/vtk/source/CMakeLists.txt +++ b/smtk/extension/vtk/source/CMakeLists.txt @@ -7,7 +7,9 @@ set(classes vtkAttributeMultiBlockSource vtkCmbLayeredConeSource vtkConeFrustum + vtkDisk vtkImplicitConeFrustum + vtkImplicitDisk vtkModelMultiBlockSource vtkModelView vtkResourceMultiBlockSource) diff --git a/smtk/extension/vtk/source/vtkDisk.cxx b/smtk/extension/vtk/source/vtkDisk.cxx new file mode 100644 index 0000000000..c8663d85b9 --- /dev/null +++ b/smtk/extension/vtk/source/vtkDisk.cxx @@ -0,0 +1,185 @@ +//========================================================================= +// 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/extension/vtk/source/vtkDisk.h" + +#include "vtkCellArray.h" +#include "vtkDoubleArray.h" +#include "vtkFloatArray.h" +#include "vtkIdTypeArray.h" +#include "vtkInformation.h" +#include "vtkInformationVector.h" +#include "vtkMath.h" +#include "vtkNew.h" +#include "vtkObjectFactory.h" +#include "vtkPointData.h" +#include "vtkPolyData.h" +#include "vtkStreamingDemandDrivenPipeline.h" +#include "vtkTransform.h" +#include "vtkVector.h" +#include "vtkVectorOperators.h" + +#include + +vtkStandardNewMacro(vtkDisk); + +vtkDisk::vtkDisk(int res) + : Resolution(res <= 3 ? 3 : res) +{ + this->SetNumberOfInputPorts(0); + this->SetNumberOfOutputPorts(NumberOfOutputs); +} + +vtkDisk::~vtkDisk() = default; + +void vtkDisk::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + os << indent << "CenterPoint: (" << this->CenterPoint[0] << ", " << this->CenterPoint[1] << ", " + << this->CenterPoint[2] << ")\n"; + os << indent << "Radius: " << this->Radius << "\n"; + os << indent << "Normal: (" << this->Normal[0] << ", " << this->Normal[1] << ", " + << this->Normal[2] << ")\n"; + os << indent << "Resolution: " << this->Resolution << "\n"; + os << indent << "Output Points Precision: " << this->OutputPointsPrecision << "\n"; +} + +int vtkDisk::RequestData( + vtkInformation* /*request*/, + vtkInformationVector** /*inputVector*/, + vtkInformationVector* outputVector) +{ + // vtkInformation* outInfo = outputVector->GetInformationObject(0); + // clang-format off + vtkPolyData* diskFace = vtkPolyData::GetData(outputVector, static_cast(OutputPorts::DiskFace)); + vtkPolyData* normEdge = vtkPolyData::GetData(outputVector, static_cast(OutputPorts::DiskNormal)); + vtkPolyData* diskEdge = vtkPolyData::GetData(outputVector, static_cast(OutputPorts::DiskEdge)); + vtkPolyData* ctrVert = vtkPolyData::GetData(outputVector, static_cast(OutputPorts::CenterVertex)); + if (!diskFace || !normEdge || !diskEdge || !ctrVert) + { + vtkErrorMacro("No output provided."); + return 0; + } + diskFace->Initialize(); + normEdge->Initialize(); + diskEdge->Initialize(); + ctrVert->Initialize(); + + vtkVector3d p0(this->CenterPoint); + vtkVector3d nn(this->Normal); + + vtkDebugMacro("DiskSource Executing"); + + // Get axes xx and yy in the plane normal to the disk axis + vtkVector3d axis = nn.Normalized(); + vtkVector3d p1 = p0 + axis * this->Radius; + vtkVector3d px(1., 0., 0.); + vtkVector3d py(0., 1., 0.); + vtkVector3d yy = axis.Cross(px); + if (yy.Norm() < 1e-10) + { + yy = axis.Cross(py); + } + yy.Normalize(); + vtkVector3d xx = yy.Cross(axis).Normalized(); + + // I. Compute the point coordinates. + vtkNew facePts; + facePts->SetDataType( + this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION ? VTK_DOUBLE : VTK_FLOAT); + facePts->SetNumberOfPoints(1 + this->Resolution); + facePts->SetPoint(this->Resolution, p0.GetData()); + + vtkNew normPts; + normPts->SetDataType( + this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION ? VTK_DOUBLE : VTK_FLOAT); + normPts->Allocate(2); + normPts->InsertNextPoint(p0.GetData()); + normPts->InsertNextPoint(p1.GetData()); + + vtkNew edgePts; + edgePts->SetDataType( + this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION ? VTK_DOUBLE : VTK_FLOAT); + edgePts->Allocate(this->Resolution); + + vtkNew centerPoint; + centerPoint->SetDataType( + this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION ? VTK_DOUBLE : VTK_FLOAT); + centerPoint->Allocate(1); + centerPoint->InsertNextPoint(p0.GetData()); + + vtkNew faceNrm; // point normals + faceNrm->SetNumberOfComponents(3); + faceNrm->SetName("normals"); + faceNrm->SetNumberOfTuples(facePts->GetNumberOfPoints()); + faceNrm->FillComponent(0, nn[0]); + faceNrm->FillComponent(1, nn[1]); + faceNrm->FillComponent(2, nn[2]); + + double angle = 2.0 * vtkMath::Pi() / this->Resolution; + + // Disk face and edge points + for (int ii = 0; ii < this->Resolution; ++ii) + { + double theta = angle * ii; + vtkVector3d pt = p0 + this->Radius * (xx * cos(theta) + yy * sin(theta)); + facePts->SetPoint(ii, pt.GetData()); + edgePts->InsertNextPoint(pt.GetData()); + } + + // II. Prepare face connectivity + vtkIdType numConnEdge = 2 + this->Resolution; + vtkIdType numConnFace = (3 + 1) * this->Resolution; + + vtkNew facePolys; + vtkNew edgeLines; + vtkNew normLines; + vtkNew centerVertex; + vtkNew faceConn; + vtkNew edgeConn; + vtkNew normConn; + faceConn->Allocate(numConnFace); + edgeConn->Allocate(numConnEdge); + normConn->SetNumberOfTuples(3); + normLines->Allocate(3); + vtkIdType zero = 0; + centerVertex->InsertNextCell(1, &zero); + + // Disk face connectivity (triangles). + // The last point is the center of the disk. + edgeConn->InsertNextValue(this->Resolution); + for (int ii = 0; ii < this->Resolution; ++ii) + { + edgeConn->InsertNextValue(ii); + faceConn->InsertNextValue(3); + faceConn->InsertNextValue(this->Resolution); + faceConn->InsertNextValue(ii); + faceConn->InsertNextValue((ii + 1) % this->Resolution); + } + normConn->SetValue(0, 2); + normConn->SetValue(1, 0); + normConn->SetValue(2, 1); + + facePolys->SetCells(this->Resolution, faceConn); + edgeLines->SetCells(1, edgeConn); + normLines->SetCells(1, normConn); + + diskFace->SetPoints(facePts); + diskFace->SetPolys(facePolys); + diskFace->GetPointData()->SetNormals(faceNrm); + normEdge->SetPoints(normPts); + normEdge->SetLines(normLines); + diskEdge->SetPoints(edgePts); + diskEdge->SetLines(edgeLines); + ctrVert->SetPoints(centerPoint); + ctrVert->SetVerts(centerVertex); + + return 1; +} diff --git a/smtk/extension/vtk/source/vtkDisk.h b/smtk/extension/vtk/source/vtkDisk.h new file mode 100644 index 0000000000..38b1859c94 --- /dev/null +++ b/smtk/extension/vtk/source/vtkDisk.h @@ -0,0 +1,121 @@ +//========================================================================= +// 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 vtkDisk_h +#define vtkDisk_h + +#include "smtk/extension/vtk/source/vtkImplicitDisk.h" // For ivar +#include "vtkPolyDataAlgorithm.h" + +#include "vtkCell.h" // Needed for VTK_CELL_SIZE + +/** + * @class vtkDisk + * @brief Generate a polygonal approximation to a disk + * + * vtkDisk creates a disk with a given center, normal, and radius. + * By default, point 1 lies at the origin with radius 0.5 + * and the normal is (0,0,1). + * The resolution specifies the number of points around the circle; + * it must be at least 3 and defaults to 32. + * + * Note that unlike many other source filters, this one is *not* + * intended for use as an input to a glyph filter or glyph mapper. + * Instead, its purpose is to provide a high-quality visual representation + * of a planar disk. + * That means it uses more points and cells than is strictly necessary + * but is able to provide a better visual quality as a result. + */ +class VTKSMTKSOURCEEXT_EXPORT vtkDisk : public vtkPolyDataAlgorithm +{ +public: + vtkTypeMacro(vtkDisk, vtkPolyDataAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkDisk(const vtkDisk&) = delete; + vtkDisk& operator=(const vtkDisk&) = delete; + + /// An enum indexing data present at each output. + enum OutputPorts + { + DiskFace = 0, //!< Triangulation of the disk face. + DiskNormal, //!< Line from the center point along the normal vector (length of disk radius). + DiskEdge, //!< Polyline of the disk boundary. + CenterVertex, //!< A single vertex at the center of the bottom face. + NumberOfOutputs + }; + + /** + * Construct with default parameters. + */ + static vtkDisk* New(); + + //@{ + /** + * Set/get the radius at the bottom of the cone. + * + * It must be non-negative. + */ + vtkSetClampMacro(Radius, double, 0.0, VTK_DOUBLE_MAX); + vtkGetMacro(Radius, double); + //@} + + //@{ + /** + * Set/get the bottom point of the cone. + * The default is 0,0,0. + */ + vtkSetVector3Macro(CenterPoint, double); + vtkGetVectorMacro(CenterPoint, double, 3); + //@} + + //@{ + /** + * Set/get the normal to the disk's plane. + * The default is 0,0,1. + */ + vtkSetVector3Macro(Normal, double); + vtkGetVectorMacro(Normal, double, 3); + //@} + + //@{ + /** + * Set/get the number of facets used to represent the conical side-face. + * + * This defaults to 32 and has a minimum of 3. + */ + vtkSetClampMacro(Resolution, int, 3, VTK_CELL_SIZE); + vtkGetMacro(Resolution, int); + //@} + + //@{ + /** + * Set/get the desired precision for the output points. + * vtkAlgorithm::SINGLE_PRECISION - Output single-precision floating point. + * vtkAlgorithm::DOUBLE_PRECISION - Output double-precision floating point. + */ + vtkSetMacro(OutputPointsPrecision, int); + vtkGetMacro(OutputPointsPrecision, int); + //@} + +protected: + vtkDisk(int res = 32); + ~vtkDisk() override; + + // int RequestInformation(vtkInformation* , vtkInformationVector** , vtkInformationVector* ) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + double CenterPoint[3]{ 0, 0, 0 }; + double Radius{ 0.5 }; + double Normal[3]{ 0, 0, 1 }; + int Resolution; + int OutputPointsPrecision{ SINGLE_PRECISION }; +}; + +#endif diff --git a/smtk/extension/vtk/source/vtkImplicitDisk.cxx b/smtk/extension/vtk/source/vtkImplicitDisk.cxx new file mode 100644 index 0000000000..9987a06482 --- /dev/null +++ b/smtk/extension/vtk/source/vtkImplicitDisk.cxx @@ -0,0 +1,113 @@ +//========================================================================= +// 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/extension/vtk/source/vtkImplicitDisk.h" + +#include "vtkCone.h" +#include "vtkMath.h" +#include "vtkObjectFactory.h" +#include "vtkPlane.h" +#include "vtkTransform.h" +#include "vtkVectorOperators.h" + +#include + +vtkStandardNewMacro(vtkImplicitDisk); + +vtkImplicitDisk::vtkImplicitDisk() + : CenterPoint{ 0, 0, 0 } + , Normal{ 0, 0, 1 } +{ +} + +vtkImplicitDisk::~vtkImplicitDisk() = default; + +void vtkImplicitDisk::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + os << indent << "CenterPoint: (" << this->CenterPoint << ")\n"; + os << indent << "Radius: " << this->Radius << "\n"; + os << indent << "Normal: (" << this->Normal << ")\n"; +} + +double vtkImplicitDisk::EvaluateFunction(double xx[3]) +{ + vtkVector3d pp(xx); + vtkVector3d delta = this->CenterPoint - pp; + double halfPlane = delta.Dot(this->Normal); + delta = delta - halfPlane * this->Normal; + double outOfDisk = delta.Norm() - this->Radius; + double outOfPlane = halfPlane < 0. ? -halfPlane : halfPlane; + // outOfPlane is >= 0. + // outOfDisk is < 0. if inside, > 0 if outside. + // The signed distance is a combination of these two distances, with its + // sign modulated by the sign of outOfDisk. + double signedDist = + std::copysign(std::sqrt(outOfDisk * outOfDisk + outOfPlane * outOfPlane), outOfDisk); + return signedDist; +} + +void vtkImplicitDisk::EvaluateGradient(double xx[3], double gg[3]) +{ + vtkVector3d pp(xx); + vtkVector3d delta = this->CenterPoint - pp; + double halfPlane = delta.Dot(this->Normal); + delta = delta - halfPlane * this->Normal; + double outOfDisk = delta.Norm() - this->Radius; + // outOfPlane is (0,0,0) on the plane with normal this->Normal passing + // through this->CenterPoint and unit length pointing away from this + // plane everywhere else in space. + vtkVector3d outOfPlane; + if (halfPlane > 0.) + { + outOfPlane = -this->Normal * halfPlane; + } + else + { + outOfPlane = this->Normal * halfPlane; + } + // inPlane = (0,0,0) at CenterPoint, unit length pointing away from disk center elswhere. + vtkVector3d inPlane = delta.Normalized() * (outOfDisk < 0 ? 0. : -outOfDisk); + vtkVector3d gradient = inPlane + outOfPlane; + gg[0] = gradient[0]; + gg[1] = gradient[1]; + gg[2] = gradient[2]; +} + +bool vtkImplicitDisk::SetRadius(double radius) +{ + if (radius <= 0.0) + { + return false; + } + this->Radius = radius; + return true; +} + +bool vtkImplicitDisk::SetCenterPoint(const vtkVector3d& pt) +{ + if (pt == this->CenterPoint) + { + return false; + } + this->CenterPoint = pt; + return true; +} + +bool vtkImplicitDisk::SetNormal(const vtkVector3d& normal) +{ + auto nn = normal.Normalized(); + if (nn == this->Normal) + { + return false; + } + this->Normal = nn; + return true; +} diff --git a/smtk/extension/vtk/source/vtkImplicitDisk.h b/smtk/extension/vtk/source/vtkImplicitDisk.h new file mode 100644 index 0000000000..eafccf9b8b --- /dev/null +++ b/smtk/extension/vtk/source/vtkImplicitDisk.h @@ -0,0 +1,121 @@ +//========================================================================= +// 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 vtkImplicitDisk_h +#define vtkImplicitDisk_h + +#include "smtk/extension/vtk/source/vtkSMTKSourceExtModule.h" // For export macro +#include "vtkImplicitFunction.h" +#include "vtkNew.h" +#include "vtkVector.h" + +class vtkCone; +class vtkPlane; +class vtkTransform; + +/** + * @class vtkImplicitDisk + * @brief Generate an implicit function whose 0-isocontour + * is a planar disk. + * + * vtkImplicitDisk creates a signed distance function for a + * disk whose center lies at a given point with the specified + * normal vector and radius. + * + * By default, point 1 (the center) lies at the origin with radius 0.5 + * and with a z-normal (0, 0, 1). + */ +class VTKSMTKSOURCEEXT_EXPORT vtkImplicitDisk : public vtkImplicitFunction +{ +public: + vtkTypeMacro(vtkImplicitDisk, vtkImplicitFunction); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkImplicitDisk(const vtkImplicitDisk&) = delete; + vtkImplicitDisk& operator=(const vtkImplicitDisk&) = delete; + + ///@{ + /** + * Evaluate our implicit function. + */ + using vtkImplicitFunction::EvaluateFunction; + double EvaluateFunction(double xx[3]) override; + ///@} + + /** + * Evaluate gradient of boolean combination. + */ + void EvaluateGradient(double xx[3], double gg[3]) override; + + /** + * Construct with default parameters. + */ + static vtkImplicitDisk* New(); + + //@{ + /** + * Set/get the radius at the bottom of the cone. + * + * It must be non-negative. + */ + virtual bool SetRadius(double radius); + vtkGetMacro(Radius, double); + //@} + + //@{ + /** + * Set/get the bottom point of the cone. + * The default is 0,0,0. + */ + virtual bool SetCenterPoint(const vtkVector3d& pt); + virtual bool SetCenterPoint(double x, double y, double z) + { + return this->SetCenterPoint(vtkVector3d(x, y, z)); + } + vtkGetMacro(CenterPoint, vtkVector3d); + //@} + + //@{ + /** + * Set/get the top point of the cone. + * The default is 0,0,0. + */ + virtual bool SetNormal(const vtkVector3d& pt); + virtual bool SetNormal(double x, double y, double z) + { + return this->SetNormal(vtkVector3d(x, y, z)); + } + virtual bool SetNormal(double* xyz) VTK_SIZEHINT(3) + { + return this->SetNormal(vtkVector3d(xyz[0], xyz[1], xyz[2])); + } + vtkGetMacro(Normal, vtkVector3d); + //@} + +protected: + vtkImplicitDisk(); + ~vtkImplicitDisk() override; + + /// Update the internal implicits and mark this object as modified. + /// + /// This is invoked by SetCenterPoint, SetRadius, SetNormal. + void UpdateImplicit(); + + // vtkNew InfiniteCone; + // vtkNew ConeTransform; + + vtkVector3d CenterPoint; + double Radius{ 0.5 }; + // vtkNew BottomPlane; + + vtkVector3d Normal; + // vtkNew TopPlane; +}; + +#endif diff --git a/smtk/extension/vtk/widgets/CMakeLists.txt b/smtk/extension/vtk/widgets/CMakeLists.txt index 77ad192158..1eaa17d5ff 100644 --- a/smtk/extension/vtk/widgets/CMakeLists.txt +++ b/smtk/extension/vtk/widgets/CMakeLists.txt @@ -1,6 +1,8 @@ set(classes vtkConeRepresentation vtkConeWidget + vtkDiskRepresentation + vtkDiskWidget vtkSBFunctionParser vtkSMTKArcRepresentation) diff --git a/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx b/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx new file mode 100644 index 0000000000..80d9fb99ee --- /dev/null +++ b/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx @@ -0,0 +1,1030 @@ +//========================================================================= +// 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/extension/vtk/widgets/vtkDiskRepresentation.h" +#include "smtk/extension/vtk/source/vtkDisk.h" +#include "smtk/extension/vtk/source/vtkImplicitDisk.h" + +#include "vtkActor.h" +#include "vtkAssemblyNode.h" +#include "vtkAssemblyPath.h" +#include "vtkBox.h" +#include "vtkCallbackCommand.h" +#include "vtkCamera.h" +#include "vtkCellArray.h" +#include "vtkCellPicker.h" +#include "vtkCommand.h" +#include "vtkDiskSource.h" +#include "vtkDoubleArray.h" +#include "vtkGlyph3DMapper.h" +#include "vtkHardwarePicker.h" +#include "vtkImageData.h" +#include "vtkInteractorObserver.h" +#include "vtkLineSource.h" +#include "vtkLookupTable.h" +#include "vtkMath.h" +#include "vtkObjectFactory.h" +#include "vtkOutlineFilter.h" +#include "vtkPickingManager.h" +#include "vtkPlane.h" +#include "vtkPointData.h" +#include "vtkPoints.h" +#include "vtkPolyData.h" +#include "vtkPolyDataMapper.h" +#include "vtkProperty.h" +#include "vtkRenderWindow.h" +#include "vtkRenderWindowInteractor.h" +#include "vtkRenderer.h" +#include "vtkSmartPointer.h" +#include "vtkSphereSource.h" +#include "vtkTransform.h" +#include "vtkTubeFilter.h" +#include "vtkVectorOperators.h" +#include "vtkWindow.h" + +#include +#include //for FLT_EPSILON + +vtkStandardNewMacro(vtkDiskRepresentation); + +vtkDiskRepresentation::vtkDiskRepresentation() +{ + this->HandleSize = 7.5; + this->Disk->SetResolution(128); + + // Set up the initial properties + this->CreateDefaultProperties(); + + for (int ii = 0; ii < NumberOfElements; ++ii) + { + this->Elements[ii].Actor->SetMapper(this->Elements[ii].Mapper); + if (ii <= DiskFace) + { + this->Elements[ii].Actor->SetProperty(this->DiskProperty); + } + else if (ii <= DiskEdge) + { + this->Elements[ii].Actor->SetProperty(this->EdgeProperty); + } + else + { + this->Elements[ii].Actor->SetProperty(this->HandleProperty); + } + } + + // Set up the pipelines for the visual elements + this->AxisTuber->SetInputConnection(this->Disk->GetOutputPort(vtkDisk::OutputPorts::DiskNormal)); + this->AxisTuber->SetNumberOfSides(12); + this->Elements[DiskNormal].Mapper->SetInputConnection(this->AxisTuber->GetOutputPort()); + this->Elements[DiskNormal].Actor->SetMapper(this->Elements[DiskNormal].Mapper); + + this->Elements[DiskFace].Mapper->SetInputConnection( + this->Disk->GetOutputPort(vtkDisk::OutputPorts::DiskFace)); + + this->Elements[DiskEdge].Mapper->SetInputConnection( + this->Disk->GetOutputPort(vtkDisk::OutputPorts::DiskEdge)); + + // Create the endpoint geometry source + this->Sphere->SetThetaResolution(16); + this->Sphere->SetPhiResolution(8); + + this->CenterVertexMapper->SetSourceConnection(this->Sphere->GetOutputPort()); + this->CenterVertexMapper->SetInputConnection( + this->Disk->GetOutputPort(vtkDisk::OutputPorts::CenterVertex)); + this->Elements[CenterVertex].Actor->SetMapper(this->CenterVertexMapper); + + // Define the point coordinates + double bounds[6]; + bounds[0] = -0.5; + bounds[1] = 0.5; + bounds[2] = -0.5; + bounds[3] = 0.5; + bounds[4] = -0.5; + bounds[5] = 0.5; + + // Initial creation of the widget, serves to initialize it + this->PlaceWidget(bounds); + + //Manage the picking stuff + this->Picker->SetTolerance(0.005); + for (int ii = DiskNormal; ii < NumberOfElements; ++ii) + { + this->Picker->AddPickList(this->Elements[ii].Actor); + } + this->Picker->PickFromListOn(); + + this->FacePicker->SetTolerance(0.005); + for (int ii = DiskFace; ii <= DiskFace; ++ii) + { + this->FacePicker->AddPickList(this->Elements[ii].Actor); + } + this->FacePicker->PickFromListOn(); + + this->RepresentationState = vtkDiskRepresentation::Outside; +} + +vtkDiskRepresentation::~vtkDiskRepresentation() = default; + +bool vtkDiskRepresentation::SetCenter(double x, double y, double z) +{ + return this->SetCenter(vtkVector3d(x, y, z)); +} + +bool vtkDiskRepresentation::SetCenter(const vtkVector3d& pt) +{ + vtkVector3d p0(this->Disk->GetCenterPoint()); + + vtkVector3d temp(pt); // Because vtkSetVectorMacro is not const correct. + if (p0 == pt) + { + // If pt is already the existing value, do nothing. + return false; + } + + this->Disk->SetCenterPoint(temp.GetData()); + this->Modified(); + return true; +} + +vtkVector3d vtkDiskRepresentation::GetCenter() const +{ + return vtkVector3d(this->Disk->GetCenterPoint()); +} + +double* vtkDiskRepresentation::GetCenterPoint() +{ + return this->Disk->GetCenterPoint(); +} + +bool vtkDiskRepresentation::SetNormal(double x, double y, double z) +{ + return this->SetNormal(vtkVector3d(x, y, z)); +} + +bool vtkDiskRepresentation::SetNormal(const vtkVector3d& nm) +{ + auto* norm = this->Disk->GetNormal(); + vtkVector3d dnorm(norm[0], norm[1], norm[2]); + if (dnorm == nm) + { + return false; + } + this->Disk->SetNormal(nm.GetData()); + return true; +} + +vtkVector3d vtkDiskRepresentation::GetNormal() const +{ + double* nm = this->Disk->GetNormal(); + return vtkVector3d(nm[0], nm[1], nm[2]); +} + +double* vtkDiskRepresentation::GetNormalVector() +{ + return this->Disk->GetNormal(); +} + +bool vtkDiskRepresentation::SetRadius(double r) +{ + double prev = this->Disk->GetRadius(); + if (prev == r) + { + return false; + } + this->Disk->SetRadius(r); + this->Modified(); + return true; +} + +double vtkDiskRepresentation::GetRadius() const +{ + return this->Disk->GetRadius(); +} + +int vtkDiskRepresentation::ComputeInteractionState(int X, int Y, int /*modify*/) +{ + // See if anything has been selected + vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->Picker); + + // The second picker may need to be called. This is done because the disk face + // may obstruct things that can be picked; thus the disk face is the selection + // of last resort. This allows users to rotate the normal vector even from behind + // the disk + if (path == nullptr) + { + this->FacePicker->Pick(X, Y, 0., this->Renderer); + path = this->FacePicker->GetPath(); + } + + if (path == nullptr) // Nothing picked + { + this->SetRepresentationState(vtkDiskRepresentation::Outside); + this->InteractionState = vtkDiskRepresentation::Outside; + return this->InteractionState; + } + + // Something picked, continue + this->ValidPick = 1; + + // Depending on the interaction state (set by the widget) we modify + // this state based on what is picked. + if (this->InteractionState == vtkDiskRepresentation::Moving) + { + vtkProp* prop = path->GetFirstNode()->GetViewProp(); + if (prop == this->Elements[DiskNormal].Actor) + { + this->InteractionState = vtkDiskRepresentation::RotatingNormal; + this->SetRepresentationState(vtkDiskRepresentation::RotatingNormal); + } + else if (prop == this->Elements[DiskEdge].Actor) + { + this->InteractionState = vtkDiskRepresentation::AdjustingRadius; + this->SetRepresentationState(vtkDiskRepresentation::AdjustingRadius); + } + else if (prop == this->Elements[CenterVertex].Actor) + { + this->InteractionState = vtkDiskRepresentation::MovingCenterVertex; + this->SetRepresentationState(vtkDiskRepresentation::MovingCenterVertex); + } + // Better to push the face along the normal than translate in view plane. + // else if (prop == this->Elements[DiskFace].Actor) + // { + // this->InteractionState = vtkDiskRepresentation::MovingWhole; + // this->SetRepresentationState(vtkDiskRepresentation::MovingWhole); + // } + else if (prop == this->Elements[DiskFace].Actor) + { + this->InteractionState = vtkDiskRepresentation::PushingDiskFace; + this->SetRepresentationState(vtkDiskRepresentation::PushingDiskFace); + } + else + { + this->InteractionState = vtkDiskRepresentation::Outside; + this->SetRepresentationState(vtkDiskRepresentation::Outside); + } + } + else + { + this->InteractionState = vtkDiskRepresentation::Outside; + } + + return this->InteractionState; +} + +void vtkDiskRepresentation::SetRepresentationState(int state) +{ + if (this->RepresentationState == state) + { + return; + } + + // Clamp the state + state = + (state < vtkDiskRepresentation::Outside + ? vtkDiskRepresentation::Outside + : (state > vtkDiskRepresentation::RotatingNormal ? vtkDiskRepresentation::RotatingNormal + : state)); + + this->RepresentationState = state; + this->Modified(); + +#if 0 + // For debugging, it is handy to see state changes: + std::cout + << " State " + << vtkDiskRepresentation::InteractionStateToString(this->RepresentationState) + << "\n"; +#endif + + this->HighlightElement(NumberOfElements, 0); // Turn everything off + if (state == vtkDiskRepresentation::RotatingNormal) + { + this->HighlightAxis(1); + } + else if (state == vtkDiskRepresentation::PushingDiskFace) + { + this->HighlightDisk(1); + } + else if (state == vtkDiskRepresentation::AdjustingRadius) + { + this->HighlightCurve(1); + } + else if (state == vtkDiskRepresentation::MovingWhole) + { + this->HighlightCurve(1); + this->HighlightDisk(1); + this->HighlightAxis(1); + this->HighlightHandle(1); + } + else + { + this->HighlightAxis(0); + this->HighlightDisk(0); + // this->HighlightOutline(0); + } +} + +void vtkDiskRepresentation::StartWidgetInteraction(double e[2]) +{ + this->StartEventPosition[0] = e[0]; + this->StartEventPosition[1] = e[1]; + this->StartEventPosition[2] = 0.0; + + this->LastEventPosition[0] = e[0]; + this->LastEventPosition[1] = e[1]; + this->LastEventPosition[2] = 0.0; +} + +void vtkDiskRepresentation::WidgetInteraction(double e[2]) +{ + // Do different things depending on state + // Calculations everybody does + double focalPoint[4], pickPoint[4], prevPickPoint[4]; + double z, vpn[3]; + + vtkCamera* camera = this->Renderer->GetActiveCamera(); + if (!camera) + { + return; + } + + // Compute the two points defining the motion vector + double pos[3]; + this->Picker->GetPickPosition(pos); + vtkInteractorObserver::ComputeWorldToDisplay(this->Renderer, pos[0], pos[1], pos[2], focalPoint); + z = focalPoint[2]; + vtkInteractorObserver::ComputeDisplayToWorld( + this->Renderer, this->LastEventPosition[0], this->LastEventPosition[1], z, prevPickPoint); + vtkInteractorObserver::ComputeDisplayToWorld(this->Renderer, e[0], e[1], z, pickPoint); + + // Process the motion + if (this->InteractionState == vtkDiskRepresentation::AdjustingRadius) + { + this->AdjustRadius(e[0], e[1], prevPickPoint, pickPoint); + } + else if (this->InteractionState == vtkDiskRepresentation::MovingCenterVertex) + { + this->TranslateCenterInPlane(prevPickPoint, pickPoint); + } + else if (this->InteractionState == vtkDiskRepresentation::MovingWhole) + { + this->TranslateCenter(prevPickPoint, pickPoint); + } + else if (this->InteractionState == vtkDiskRepresentation::PushingDiskFace) + { + this->PushFace(prevPickPoint, pickPoint); + } + else if (this->InteractionState == vtkDiskRepresentation::RotatingNormal) + { + camera->GetViewPlaneNormal(vpn); + this->Rotate(e[0], e[1], prevPickPoint, pickPoint, vpn); + } + + this->LastEventPosition[0] = e[0]; + this->LastEventPosition[1] = e[1]; + this->LastEventPosition[2] = 0.0; +} + +void vtkDiskRepresentation::EndWidgetInteraction(double /*newEventPos*/[2]) +{ + this->SetRepresentationState(vtkDiskRepresentation::Outside); +} + +double* vtkDiskRepresentation::GetBounds() +{ + this->BuildRepresentation(); + this->BoundingBox->SetBounds(this->Elements[DiskFace].Actor->GetBounds()); + for (int ii = DiskFace; ii < NumberOfElements; ++ii) + { + this->BoundingBox->AddBounds(this->Elements[ii].Actor->GetBounds()); + } + + return this->BoundingBox->GetBounds(); +} + +void vtkDiskRepresentation::GetActors(vtkPropCollection* pc) +{ + for (int ii = 0; ii < NumberOfElements; ++ii) + { + this->Elements[ii].Actor->GetActors(pc); + } +} + +void vtkDiskRepresentation::ReleaseGraphicsResources(vtkWindow* w) +{ + for (int ii = 0; ii < NumberOfElements; ++ii) + { + this->Elements[ii].Actor->ReleaseGraphicsResources(w); + } +} + +int vtkDiskRepresentation::RenderOpaqueGeometry(vtkViewport* v) +{ + int count = 0; + this->BuildRepresentation(); + for (int ii = DiskNormal; ii < NumberOfElements; ++ii) + { + count += this->Elements[ii].Actor->RenderOpaqueGeometry(v); + } + + if (this->DrawDisk) + { + for (int ii = DiskFace; ii < DiskNormal; ++ii) + { + count += this->Elements[ii].Actor->RenderOpaqueGeometry(v); + } + } + + return count; +} + +int vtkDiskRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* v) +{ + int count = 0; + this->BuildRepresentation(); + for (int ii = DiskNormal; ii < NumberOfElements; ++ii) + { + count += this->Elements[ii].Actor->RenderTranslucentPolygonalGeometry(v); + } + + if (this->DrawDisk) + { + for (int ii = DiskFace; ii < DiskNormal; ++ii) + { + count += this->Elements[ii].Actor->RenderTranslucentPolygonalGeometry(v); + } + } + + return count; +} + +vtkTypeBool vtkDiskRepresentation::HasTranslucentPolygonalGeometry() +{ + int result = 0; + for (int ii = DiskNormal; ii < NumberOfElements; ++ii) + { + result |= this->Elements[ii].Actor->HasTranslucentPolygonalGeometry(); + } + + if (this->DrawDisk) + { + for (int ii = DiskFace; ii < DiskNormal; ++ii) + { + result |= this->Elements[ii].Actor->HasTranslucentPolygonalGeometry(); + } + } + + return result; +} + +void vtkDiskRepresentation::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "Resolution: " << this->Resolution << "\n"; + + if (this->HandleProperty) + { + os << indent << "Handle Property: " << this->HandleProperty << "\n"; + } + else + { + os << indent << "Handle Property: (none)\n"; + } + if (this->SelectedHandleProperty) + { + os << indent << "Selected Handle Property: " << this->SelectedHandleProperty << "\n"; + } + else + { + os << indent << "Selected Handle Property: (none)\n"; + } + + if (this->DiskProperty) + { + os << indent << "Disk Property: " << this->DiskProperty << "\n"; + } + else + { + os << indent << "Disk Property: (none)\n"; + } + if (this->SelectedDiskProperty) + { + os << indent << "Selected Disk Property: " << this->SelectedDiskProperty << "\n"; + } + else + { + os << indent << "Selected Disk Property: (none)\n"; + } + + if (this->EdgeProperty) + { + os << indent << "Edge Property: " << this->EdgeProperty << "\n"; + } + else + { + os << indent << "Edge Property: (none)\n"; + } + + os << indent << "Along X Axis: " << (this->AlongXAxis ? "On" : "Off") << "\n"; + os << indent << "Along Y Axis: " << (this->AlongYAxis ? "On" : "Off") << "\n"; + os << indent << "ALong Z Axis: " << (this->AlongZAxis ? "On" : "Off") << "\n"; + + os << indent << "Tubing: " << (this->Tubing ? "On" : "Off") << "\n"; + os << indent << "Draw Disk: " << (this->DrawDisk ? "On" : "Off") << "\n"; + os << indent << "Bump Distance: " << this->BumpDistance << "\n"; + + os << indent << "Representation State: " + << vtkDiskRepresentation::InteractionStateToString(this->RepresentationState) << "\n"; + + // this->InteractionState is printed in superclass + // this is commented to avoid PrintSelf errors +} + +void vtkDiskRepresentation::HighlightElement(ElementType elem, int highlight) +{ + switch (elem) + { + case DiskFace: + this->HighlightDisk(highlight); + break; + case DiskNormal: + this->HighlightAxis(highlight); + break; + case DiskEdge: + this->HighlightCurve(highlight); + break; + case CenterVertex: + this->HighlightHandle(highlight); + break; + case NumberOfElements: + // Set everything to the given highlight state. + this->HighlightAxis(highlight); + this->HighlightDisk(highlight); + this->HighlightCurve(highlight); + this->HighlightHandle(highlight); + break; + } +} + +void vtkDiskRepresentation::HighlightDisk(int highlight) +{ + if (highlight) + { + this->Elements[DiskFace].Actor->SetProperty(this->SelectedDiskProperty); + } + else + { + this->Elements[DiskFace].Actor->SetProperty(this->DiskProperty); + } +} + +void vtkDiskRepresentation::HighlightAxis(int highlight) +{ + if (highlight) + { + this->Elements[DiskNormal].Actor->SetProperty(this->SelectedEdgeProperty); + } + else + { + this->Elements[DiskNormal].Actor->SetProperty(this->EdgeProperty); + } +} + +void vtkDiskRepresentation::HighlightCurve(int highlight) +{ + ElementType elem = DiskEdge; + if (highlight) + { + this->Elements[elem].Actor->SetProperty(this->SelectedEdgeProperty); + } + else + { + this->Elements[elem].Actor->SetProperty(this->EdgeProperty); + } +} + +void vtkDiskRepresentation::HighlightHandle(int highlight) +{ + ElementType elem = CenterVertex; + if (highlight) + { + this->Elements[elem].Actor->SetProperty(this->SelectedHandleProperty); + } + else + { + this->Elements[elem].Actor->SetProperty(this->HandleProperty); + } +} + +void vtkDiskRepresentation::Rotate(double X, double Y, double* p1, double* p2, double* vpn) +{ + double v[3]; //vector of motion + double axis[3]; //axis of rotation + double theta; //rotation angle + + // mouse motion vector in world space + v[0] = p2[0] - p1[0]; + v[1] = p2[1] - p1[1]; + v[2] = p2[2] - p1[2]; + + vtkVector3d cp0(this->Disk->GetCenterPoint()); + vtkVector3d cp1(this->Disk->GetNormal()); + vtkVector3d center = cp0; + + // Create axis of rotation and angle of rotation + vtkMath::Cross(vpn, v, axis); + if (vtkMath::Normalize(axis) == 0.0) + { + return; + } + int* size = this->Renderer->GetSize(); + double l2 = (X - this->LastEventPosition[0]) * (X - this->LastEventPosition[0]) + + (Y - this->LastEventPosition[1]) * (Y - this->LastEventPosition[1]); + theta = 360.0 * sqrt(l2 / (size[0] * size[0] + size[1] * size[1])); + + // Manipulate the transform to reflect the rotation + this->Transform->Identity(); + this->Transform->Translate(center[0], center[1], center[2]); + this->Transform->RotateWXYZ(theta, axis); + this->Transform->Translate(-center[0], -center[1], -center[2]); + + // cp0 is the center of rotation, it will not move. + // this->Transform->TransformPoint(cp0.GetData(), cp0.GetData()); + // this->Transform->TransformPoint(cp1.GetData(), cp1.GetData()); + this->Transform->TransformVector(cp1.GetData(), cp1.GetData()); + + // this->Disk->SetCenterPoint(cp0.GetData()); + // this->Disk->SetTopPoint(cp1.GetData()); + this->Disk->SetNormal(cp1.GetData()); +} + +void vtkDiskRepresentation::PushFace(double* p1, double* p2) +{ + //Get the motion vector + vtkVector3d v; + v[0] = p2[0] - p1[0]; + v[1] = p2[1] - p1[1]; + v[2] = p2[2] - p1[2]; + + vtkVector3d cp0(this->Disk->GetCenterPoint()); + vtkVector3d cp1(this->Disk->GetNormal()); + vtkVector3d axis = cp1; + axis.Normalize(); + double bump = v.Dot(axis); + + this->Disk->SetCenterPoint((cp0 + bump * axis).GetData()); +} + +// Loop through all points and translate them +void vtkDiskRepresentation::AdjustRadius(double /*X*/, double Y, double* p1, double* p2) +{ + if (Y == this->LastEventPosition[1]) + { + return; + } + + double dr; + double radius = this->Disk->GetRadius(); + double v[3]; //vector of motion + v[0] = p2[0] - p1[0]; + v[1] = p2[1] - p1[1]; + v[2] = p2[2] - p1[2]; + double l = sqrt(vtkMath::Dot(v, v)); + + dr = l / 4; + if (Y < this->LastEventPosition[1]) + { + dr *= -1.0; + } + + double nextRadius = radius + dr; + + if (nextRadius < 1e-30) + { + nextRadius = 1e-30; + } + else if (nextRadius < 0.) + { + nextRadius = -nextRadius; + } + this->Disk->SetRadius(nextRadius); + this->BuildRepresentation(); +} + +// Loop through all points and translate them +void vtkDiskRepresentation::TranslateCenter(double* p1, double* p2) +{ + //Get the motion vector + vtkVector3d v; + v[0] = p2[0] - p1[0]; + v[1] = p2[1] - p1[1]; + v[2] = p2[2] - p1[2]; + + vtkVector3d cp0(this->Disk->GetCenterPoint()); + + this->Disk->SetCenterPoint((cp0 + v).GetData()); + this->BuildRepresentation(); +} + +// Translate the center point within the plane of the disk. +void vtkDiskRepresentation::TranslateCenterInPlane(double* p1, double* p2) +{ + // Get the motion vector + vtkVector3d v; + v[0] = p2[0] - p1[0]; + v[1] = p2[1] - p1[1]; + v[2] = p2[2] - p1[2]; + + vtkVector3d cp(this->Disk->GetCenterPoint()); + vtkVector3d norm(this->Disk->GetNormal()); + + v = v - v.Dot(norm) * norm; + + this->Disk->SetCenterPoint((cp + v).GetData()); + this->BuildRepresentation(); +} + +// Loop through all points and translate them +void vtkDiskRepresentation::TranslateHandle(double* p1, double* p2) +{ + //Get the motion vector + vtkVector3d v; + v[0] = p2[0] - p1[0]; + v[1] = p2[1] - p1[1]; + v[2] = p2[2] - p1[2]; + + vtkVector3d handle(this->Disk->GetCenterPoint()); + + this->Disk->SetCenterPoint((handle + v).GetData()); + this->BuildRepresentation(); +} + +void vtkDiskRepresentation::SizeHandles() +{ + double radius = + this->vtkWidgetRepresentation::SizeHandlesInPixels(1.5, this->Sphere->GetCenter()); + + this->Sphere->SetRadius(radius); + this->AxisTuber->SetRadius(0.25 * radius); +} + +void vtkDiskRepresentation::CreateDefaultProperties() +{ + this->HandleProperty->SetColor(1., 1., 1.); + + this->EdgeProperty->SetColor(1., 1., 1.); + this->EdgeProperty->SetLineWidth(3); + + this->DiskProperty->SetColor(1., 1., 1.); + this->DiskProperty->SetOpacity(0.5); + + this->SelectedHandleProperty->SetColor(0.0, 1.0, 0.); + this->SelectedHandleProperty->SetAmbient(1.0); + + this->SelectedEdgeProperty->SetColor(0., 1.0, 0.0); + this->SelectedEdgeProperty->SetLineWidth(3); + + this->SelectedDiskProperty->SetColor(0., 1., 0.); + this->SelectedDiskProperty->SetOpacity(0.5); +} + +void vtkDiskRepresentation::PlaceWidget(double bds[6]) +{ + vtkVector3d lo(bds[0], bds[2], bds[4]); + vtkVector3d hi(bds[1], bds[3], bds[5]); + vtkVector3d md = 0.5 * (lo + hi); + + this->InitialLength = (hi - lo).Norm(); + + if (this->AlongYAxis) + { + this->Disk->SetCenterPoint(md[0], lo[1], md[2]); + double radius = hi[2] - md[2] > hi[0] - md[0] ? hi[0] - md[0] : hi[2] - md[2]; + this->Disk->SetRadius(radius); + } + else if (this->AlongZAxis) + { + this->Disk->SetCenterPoint(md[0], md[1], lo[2]); + double radius = hi[0] - md[0] > hi[1] - md[1] ? hi[1] - md[1] : hi[0] - md[0]; + this->Disk->SetRadius(radius); + } + else //default or x-normal + { + this->Disk->SetCenterPoint(lo[0], md[1], md[2]); + double radius = hi[2] - md[2] > hi[1] - md[1] ? hi[1] - md[1] : hi[2] - md[2]; + this->Disk->SetRadius(radius); + } + + this->ValidPick = 1; // since we have positioned the widget successfully + this->BuildRepresentation(); +} + +void vtkDiskRepresentation::SetDrawDisk(vtkTypeBool drawCyl) +{ + if (drawCyl == this->DrawDisk) + { + return; + } + + this->Modified(); + this->DrawDisk = drawCyl; + this->BuildRepresentation(); +} + +void vtkDiskRepresentation::SetAlongXAxis(vtkTypeBool var) +{ + if (this->AlongXAxis != var) + { + this->AlongXAxis = var; + this->Modified(); + } + if (var) + { + this->AlongYAxisOff(); + this->AlongZAxisOff(); + } +} + +void vtkDiskRepresentation::SetAlongYAxis(vtkTypeBool var) +{ + if (this->AlongYAxis != var) + { + this->AlongYAxis = var; + this->Modified(); + } + if (var) + { + this->AlongXAxisOff(); + this->AlongZAxisOff(); + } +} + +void vtkDiskRepresentation::SetAlongZAxis(vtkTypeBool var) +{ + if (this->AlongZAxis != var) + { + this->AlongZAxis = var; + this->Modified(); + } + if (var) + { + this->AlongXAxisOff(); + this->AlongYAxisOff(); + } +} + +void vtkDiskRepresentation::GetDisk(vtkImplicitDisk* disk) +{ + if (disk == nullptr) + { + return; + } + + disk->SetCenterPoint(this->GetCenter()); + disk->SetRadius(this->Disk->GetRadius()); + disk->SetNormal(this->Disk->GetNormal()); +} + +void vtkDiskRepresentation::UpdatePlacement() +{ + this->BuildRepresentation(); +} + +void vtkDiskRepresentation::BumpDisk(int dir, double factor) +{ + // Compute the distance + double d = this->InitialLength * this->BumpDistance * factor; + + // Push the cylinder + this->PushDisk((dir > 0 ? d : -d)); +} + +void vtkDiskRepresentation::PushDisk(double d) +{ + vtkCamera* camera = this->Renderer->GetActiveCamera(); + if (!camera) + { + return; + } + vtkVector3d vpn; + vtkVector3d p0(this->Disk->GetCenterPoint()); + vtkVector3d p1(this->Disk->GetNormal()); + camera->GetViewPlaneNormal(vpn.GetData()); + + p0 = p0 + d * vpn; + + this->Disk->SetCenterPoint(p0.GetData()); + this->BuildRepresentation(); +} + +bool vtkDiskRepresentation::PickNormal(int X, int Y, bool snapToMeshPoint) +{ + this->HardwarePicker->SetSnapToMeshPoint(snapToMeshPoint); + vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->HardwarePicker); + if (path == nullptr) // actors of renderer were not touched + { + if (this->PickCameraFocalInfo) + { + double normal[3]; + this->HardwarePicker->GetPickNormal(normal); + this->SetNormal(normal); + this->BuildRepresentation(); + } + return this->PickCameraFocalInfo; + } + else // actors of renderer were touched + { + double normal[3]; + this->HardwarePicker->GetPickNormal(normal); + if (!std::isnan(normal[0]) || !std::isnan(normal[1]) || !std::isnan(normal[2])) + { + this->SetNormal(normal); + this->BuildRepresentation(); + return true; + } + else + { + return false; + } + } +} + +std::string vtkDiskRepresentation::InteractionStateToString(int state) +{ + switch (state) + { + case Outside: + return "Outside"; + break; + case Moving: + return "Moving"; + break; + case PushingDiskFace: + return "PushingDiskFace"; + break; + case AdjustingRadius: + return "AdjustingRadius"; + break; + case MovingCenterVertex: + return "MovingCenterVertex"; + break; + case MovingWhole: + return "MovingWhole"; + break; + case RotatingNormal: + return "RotatingNormal"; + break; + default: + break; + } + return "Invalid"; +} + +void vtkDiskRepresentation::BuildRepresentation() +{ + if (!this->Renderer || !this->Renderer->GetRenderWindow()) + { + return; + } + + vtkInformation* info = this->GetPropertyKeys(); + for (int ii = 0; ii < NumberOfElements; ++ii) + { + this->Elements[ii].Actor->SetPropertyKeys(info); + } + + if ( + this->GetMTime() > this->BuildTime || this->Disk->GetMTime() > this->BuildTime || + this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime) + { + // Control the look of the edges + if (this->Tubing) + { + this->Elements[DiskNormal].Mapper->SetInputConnection(this->AxisTuber->GetOutputPort()); + } + else + { + this->Elements[DiskNormal].Mapper->SetInputConnection( + this->Disk->GetOutputPort(vtkDisk::OutputPorts::DiskNormal)); + } + + this->SizeHandles(); + this->BuildTime.Modified(); + } +} + +void vtkDiskRepresentation::RegisterPickers() +{ + vtkPickingManager* pm = this->GetPickingManager(); + if (!pm) + { + return; + } + pm->AddPicker(this->Picker, this); +} diff --git a/smtk/extension/vtk/widgets/vtkDiskRepresentation.h b/smtk/extension/vtk/widgets/vtkDiskRepresentation.h new file mode 100644 index 0000000000..9353a4fe47 --- /dev/null +++ b/smtk/extension/vtk/widgets/vtkDiskRepresentation.h @@ -0,0 +1,383 @@ +//========================================================================= +// 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 vtkDiskRepresentation_h +#define vtkDiskRepresentation_h + +#include "smtk/extension/vtk/widgets/vtkSMTKWidgetsExtModule.h" // For export macro +#include "vtkNew.h" +#include "vtkVector.h" +#include "vtkWidgetRepresentation.h" + +#include + +class vtkActor; +class vtkPolyDataMapper; +class vtkHardwarePicker; +class vtkCellPicker; +class vtkDisk; +class vtkGlyph3DMapper; +class vtkImplicitDisk; +class vtkSphereSource; +class vtkTubeFilter; +class vtkProperty; +class vtkImageData; +class vtkOutlineFilter; +class vtkFeatureEdges; +class vtkPolyData; +class vtkPolyDataAlgorithm; +class vtkTransform; +class vtkBox; +class vtkLookupTable; + +#define VTK_MAX_DISK_RESOLUTION 2048 + +/** + * @class vtkDiskRepresentation + * @brief defining the representation for a planar disk. + * + * This class is a concrete representation for the + * vtkDiskWidget. It represents a finite disk + * defined by a center point, a normal vector, and a radius. + * This disk representation can be manipulated by using the + * vtkDiskWidget. + * + * @sa + * vtkDiskWidget +*/ +class VTKSMTKWIDGETSEXT_EXPORT vtkDiskRepresentation : public vtkWidgetRepresentation +{ +public: + /** + * Instantiate the class. + */ + static vtkDiskRepresentation* New(); + + //@{ + /** + * Standard methods for the class. + */ + vtkTypeMacro(vtkDiskRepresentation, vtkWidgetRepresentation); + void PrintSelf(ostream& os, vtkIndent indent) override; + //@} + + vtkDiskRepresentation(const vtkDiskRepresentation&) = delete; + vtkDiskRepresentation& operator=(const vtkDiskRepresentation&) = delete; + + //@{ + /** + * Set/get the center of the disk. + */ + bool SetCenter(double x, double y, double z); + bool SetCenter(const vtkVector3d& pt); + vtkVector3d GetCenter() const; + void SetCenterPoint(double* x) VTK_SIZEHINT(3) { this->SetCenter(x[0], x[1], x[2]); } + double* GetCenterPoint() VTK_SIZEHINT(3); + //@} + + //@{ + /** + * Set/get the normal of the disk. + */ + bool SetNormal(double x, double y, double z); + bool SetNormal(const vtkVector3d& nm); + vtkVector3d GetNormal() const; + void SetNormal(double* x) VTK_SIZEHINT(3) { this->SetNormal(x[0], x[1], x[2]); } + double* GetNormalVector() VTK_SIZEHINT(3); + //@} + + //@{ + /** + * Set/get the radius of the disk. + * + * Negative values are generally a bad idea but not prohibited at this point. + * They will result in bad surface normals, though. + */ + bool SetRadius(double r); + double GetRadius() const; + //@} + + //@{ + /** + * Force the disk widget's normal to be aligned with one of the x-y-z axes. + * If one axis is set on, the other two will be set off. + * Remember that when the state changes, a ModifiedEvent is invoked. + * This can be used to snap the disk to the axes if it is originally + * not aligned. + */ + void SetAlongXAxis(vtkTypeBool); + vtkGetMacro(AlongXAxis, vtkTypeBool); + vtkBooleanMacro(AlongXAxis, vtkTypeBool); + void SetAlongYAxis(vtkTypeBool); + vtkGetMacro(AlongYAxis, vtkTypeBool); + vtkBooleanMacro(AlongYAxis, vtkTypeBool); + void SetAlongZAxis(vtkTypeBool); + vtkGetMacro(AlongZAxis, vtkTypeBool); + vtkBooleanMacro(AlongZAxis, vtkTypeBool); + //@} + + //@{ + /** + * Enable/disable the drawing of the disk. In some cases the disk + * interferes with the object that it is operating on (e.g., the + * disk interferes with the cut surface it produces resulting in + * z-buffer artifacts.) By default it is off. + */ + void SetDrawDisk(vtkTypeBool drawCyl); + vtkGetMacro(DrawDisk, vtkTypeBool); + vtkBooleanMacro(DrawDisk, vtkTypeBool); + //@} + + //@{ + /** + * Set/Get the resolution of the disk. This is the number of + * triangles used to approximate the circular face and line + * segments used to approximate the circular edge. + * A vtkDisk is used under the hood. + */ + vtkSetClampMacro(Resolution, int, 3, VTK_MAX_DISK_RESOLUTION); + vtkGetMacro(Resolution, int); + //@} + + //@{ + /** + * Turn on/off tubing of the wire outline of the cylinder + * intersection (against the bounding box). The tube thickens the + * line by wrapping with a vtkTubeFilter. + */ + vtkSetMacro(Tubing, vtkTypeBool); + vtkGetMacro(Tubing, vtkTypeBool); + vtkBooleanMacro(Tubing, vtkTypeBool); + //@} + + /** + * Get the implicit function for the disk. The user must provide an instance + * vtkImplicitDisk; upon return, it will be set to the difference between + * an infinite disk and the two cutting planes. + * The returned implicit can be used by a variety of filters + * to perform clipping, cutting, and selection of data. + */ + void GetDisk(vtkImplicitDisk* disk); + + /** + * Satisfies the superclass API. This will change the state of the widget + * to match changes that have been made to the underlying PolyDataSource. + */ + void UpdatePlacement(); + + //@{ + /** + * Get the properties used to render the center point + * when selected or not. + */ + vtkGetObjectMacro(HandleProperty, vtkProperty); + vtkGetObjectMacro(SelectedHandleProperty, vtkProperty); + //@} + + //@{ + /** + * Get the disk-face properties. The properties of the disk when selected + * and unselected can be manipulated. + */ + vtkGetObjectMacro(DiskProperty, vtkProperty); + vtkGetObjectMacro(SelectedDiskProperty, vtkProperty); + //@} + + //@{ + /** + * Get the property of the boundary edge and normal line. (This property also + * applies to the edges when tubed.) + */ + vtkGetObjectMacro(EdgeProperty, vtkProperty); + //@} + + //@{ + /** + * Methods to interface with the vtkDiskWidget. + */ + int ComputeInteractionState(int X, int Y, int modify = 0) override; + void PlaceWidget(double bounds[6]) override; + void BuildRepresentation() override; + void StartWidgetInteraction(double eventPos[2]) override; + void WidgetInteraction(double newEventPos[2]) override; + void EndWidgetInteraction(double newEventPos[2]) override; + //@} + + //@{ + /** + * Methods supporting the rendering process. + */ + double* GetBounds() override; + void GetActors(vtkPropCollection* pc) override; + void ReleaseGraphicsResources(vtkWindow*) override; + int RenderOpaqueGeometry(vtkViewport*) override; + int RenderTranslucentPolygonalGeometry(vtkViewport*) override; + vtkTypeBool HasTranslucentPolygonalGeometry() override; + //@} + + //@{ + /** + * Specify a translation distance used by the BumpDisk() method. Note that the + * distance is normalized; it is the fraction of the length of the bounding + * box of the wire outline. + */ + vtkSetClampMacro(BumpDistance, double, 0.000001, 1); + vtkGetMacro(BumpDistance, double); + //@} + + /** + * Translate the disk in the direction of the view vector by the + * specified BumpDistance. The dir parameter controls which + * direction the pushing occurs, either in the same direction as the + * view vector, or when negative, in the opposite direction. The factor + * controls what percentage of the bump is used. + */ + void BumpDisk(int dir, double factor); + + /** + * Push the disk the distance specified along the view + * vector. Positive values are in the direction of the view vector; + * negative values are in the opposite direction. The distance value + * is expressed in world coordinates. + */ + void PushDisk(double distance); + + /**\brief Set the disk normal to either the surface normal below the pointer + * or the opposite of the camera view vector (pointing back toward the camera). + */ + bool PickNormal(int X, int Y, bool snapToMeshPoint); + + // Manage the state of the widget + enum _InteractionState + { + Outside = 0, + Moving, + PushingDiskFace, + AdjustingRadius, + MovingCenterVertex, + MovingWhole, + RotatingNormal + }; + + static std::string InteractionStateToString(int); + + //@{ + /** + * The interaction state may be set from a widget (e.g., + * vtkImplicitCylinderWidget) or other object. This controls how the + * interaction with the widget proceeds. Normally this method is used as + * part of a handshaking process with the widget: First + * ComputeInteractionState() is invoked that returns a state based on + * geometric considerations (i.e., cursor near a widget feature), then + * based on events, the widget may modify this further. + */ + vtkSetClampMacro(InteractionState, int, Outside, RotatingNormal); + //@} + + //@{ + /** + * Sets the visual appearance of the representation based on the + * state it is in. This state is usually the same as InteractionState. + */ + virtual void SetRepresentationState(int); + vtkGetMacro(RepresentationState, int); + //@} + + /* + * Register internal Pickers within PickingManager + */ + void RegisterPickers() override; + +protected: + vtkDiskRepresentation(); + ~vtkDiskRepresentation() override; + + /// Visual elements of the representation. + enum ElementType + { + DiskFace = 0, + DiskNormal, + DiskEdge, + CenterVertex, + NumberOfElements + }; + + void HighlightElement(ElementType elem, int highlight); + + void HighlightDisk(int highlight); + void HighlightAxis(int highlight); + void HighlightCurve(int highlight); + void HighlightHandle(int highlight); + + // Methods to manipulate the disk + void Rotate(double X, double Y, double* p1, double* p2, double* vpn); + void TranslateDisk(double* p1, double* p2); + void PushFace(double* p1, double* p2); + void AdjustRadius(double X, double Y, double* p1, double* p2); + void TranslateCenter(double* p1, double* p2); + void TranslateCenterInPlane(double* p1, double* p2); + void TranslateHandle(double* p1, double* p2); + void SizeHandles(); + + void CreateDefaultProperties(); + + struct Element + { + vtkNew Actor; + vtkNew Mapper; + }; + + // Actors and mappers for all visual elements of the representation. + std::array Elements; + + int RepresentationState; + // Keep track of event positions + double LastEventPosition[3]; + // Controlling the push operation + double BumpDistance{ 0.01 }; + // Controlling ivars + vtkTypeBool AlongXAxis{ 0 }; + vtkTypeBool AlongYAxis{ 0 }; + vtkTypeBool AlongZAxis{ 0 }; + // The actual disk which is being manipulated + vtkNew Disk; + // The facet resolution for rendering purposes. + int Resolution{ 128 }; + vtkTypeBool DrawDisk{ 1 }; + vtkNew AxisTuber; // Used to style edges. + vtkTypeBool Tubing{ 1 }; //control whether tubing is on + + // Source of center-point handle geometry + vtkNew Sphere; + + // Do the picking + vtkNew Picker; + vtkNew FacePicker; + vtkNew HardwarePicker; // For picking surface normals. + vtkTypeBool PickCameraFocalInfo{ true }; // Allow picking the camera projection direction. + + // Properties used to control the appearance of selected objects and + // the manipulator in general. + vtkNew HandleProperty; + vtkNew SelectedHandleProperty; + vtkNew DiskProperty; + vtkNew SelectedDiskProperty; + vtkNew EdgeProperty; + vtkNew SelectedEdgeProperty; + + // Support GetBounds() method + vtkNew BoundingBox; + + // Overrides for the point-handle polydata mappers + vtkNew CenterVertexMapper; + + vtkNew Transform; +}; + +#endif diff --git a/smtk/extension/vtk/widgets/vtkDiskWidget.cxx b/smtk/extension/vtk/widgets/vtkDiskWidget.cxx new file mode 100644 index 0000000000..8e7c3bd38c --- /dev/null +++ b/smtk/extension/vtk/widgets/vtkDiskWidget.cxx @@ -0,0 +1,428 @@ +//========================================================================= +// 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/extension/vtk/widgets/vtkDiskWidget.h" +#include "smtk/extension/vtk/widgets/vtkDiskRepresentation.h" + +#include "vtkCallbackCommand.h" +#include "vtkCamera.h" +#include "vtkCommand.h" +#include "vtkEvent.h" +#include "vtkObjectFactory.h" +#include "vtkRenderWindow.h" +#include "vtkRenderWindowInteractor.h" +#include "vtkRenderer.h" +#include "vtkStdString.h" +#include "vtkWidgetCallbackMapper.h" +#include "vtkWidgetEvent.h" +#include "vtkWidgetEventTranslator.h" + +vtkStandardNewMacro(vtkDiskWidget); + +//---------------------------------------------------------------------------- +vtkDiskWidget::vtkDiskWidget() +{ + this->WidgetState = vtkDiskWidget::Start; + + // Define widget events + this->CallbackMapper->SetCallbackMethod( + vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select, this, vtkDiskWidget::SelectAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::LeftButtonReleaseEvent, + vtkWidgetEvent::EndSelect, + this, + vtkDiskWidget::EndSelectAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::MiddleButtonPressEvent, + vtkWidgetEvent::Translate, + this, + vtkDiskWidget::TranslateAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::MiddleButtonReleaseEvent, + vtkWidgetEvent::EndTranslate, + this, + vtkDiskWidget::EndSelectAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::RightButtonPressEvent, vtkWidgetEvent::Scale, this, vtkDiskWidget::ScaleAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::RightButtonReleaseEvent, + vtkWidgetEvent::EndScale, + this, + vtkDiskWidget::EndSelectAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::MouseMoveEvent, vtkWidgetEvent::Move, this, vtkDiskWidget::MoveAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 30, + 1, + "Up", + vtkWidgetEvent::Up, + this, + vtkDiskWidget::MoveDiskAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 28, + 1, + "Right", + vtkWidgetEvent::Up, + this, + vtkDiskWidget::MoveDiskAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 31, + 1, + "Down", + vtkWidgetEvent::Down, + this, + vtkDiskWidget::MoveDiskAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 29, + 1, + "Left", + vtkWidgetEvent::Down, + this, + vtkDiskWidget::MoveDiskAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 'n', + 1, + "n", + vtkWidgetEvent::PickNormal, + this, + vtkDiskWidget::PickNormalAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 'N', + 1, + "N", + vtkWidgetEvent::PickNormal, + this, + vtkDiskWidget::PickNormalAction); + this->CallbackMapper->SetCallbackMethod( + vtkCommand::KeyPressEvent, + vtkEvent::AnyModifier, + 14, + 1, + "n", + vtkWidgetEvent::PickNormal, + this, + vtkDiskWidget::PickNormalAction); +} + +//---------------------------------------------------------------------------- +vtkDiskWidget::~vtkDiskWidget() = default; + +//---------------------------------------------------------------------- +void vtkDiskWidget::SelectAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + // Get the event position + int X = self->Interactor->GetEventPosition()[0]; + int Y = self->Interactor->GetEventPosition()[1]; + + // We want to update the radius, normal, and center as appropriate + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(vtkDiskRepresentation::Moving); + int interactionState = self->WidgetRep->ComputeInteractionState(X, Y); + self->UpdateCursorShape(interactionState); + + if (self->WidgetRep->GetInteractionState() == vtkDiskRepresentation::Outside) + { + return; + } + + if (self->Interactor->GetControlKey() && interactionState == vtkDiskRepresentation::MovingWhole) + { + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(vtkDiskRepresentation::MovingWhole); + } + + // We are definitely selected + self->GrabFocus(self->EventCallbackCommand); + double eventPos[2]; + eventPos[0] = static_cast(X); + eventPos[1] = static_cast(Y); + self->WidgetState = vtkDiskWidget::Active; + self->WidgetRep->StartWidgetInteraction(eventPos); + + self->EventCallbackCommand->SetAbortFlag(1); + self->StartInteraction(); + self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); + self->Render(); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::TranslateAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + // Get the event position + int X = self->Interactor->GetEventPosition()[0]; + int Y = self->Interactor->GetEventPosition()[1]; + + // We want to compute an orthogonal vector to the pane that has been selected + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(vtkDiskRepresentation::Moving); + int interactionState = self->WidgetRep->ComputeInteractionState(X, Y); + self->UpdateCursorShape(interactionState); + + if (self->WidgetRep->GetInteractionState() == vtkDiskRepresentation::Outside) + { + return; + } + + // We are definitely selected + self->GrabFocus(self->EventCallbackCommand); + double eventPos[2]; + eventPos[0] = static_cast(X); + eventPos[1] = static_cast(Y); + self->WidgetState = vtkDiskWidget::Active; + self->WidgetRep->StartWidgetInteraction(eventPos); + + self->EventCallbackCommand->SetAbortFlag(1); + self->StartInteraction(); + self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); + self->Render(); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::ScaleAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + // Get the event position + int X = self->Interactor->GetEventPosition()[0]; + int Y = self->Interactor->GetEventPosition()[1]; + + // We want to compute an orthogonal vector to the pane that has been selected + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(vtkDiskRepresentation::AdjustingRadius); + int interactionState = self->WidgetRep->ComputeInteractionState(X, Y); + self->UpdateCursorShape(interactionState); + + if (self->WidgetRep->GetInteractionState() == vtkDiskRepresentation::Outside) + { + return; + } + + // We are definitely selected + self->GrabFocus(self->EventCallbackCommand); + double eventPos[2]; + eventPos[0] = static_cast(X); + eventPos[1] = static_cast(Y); + self->WidgetState = vtkDiskWidget::Active; + self->WidgetRep->StartWidgetInteraction(eventPos); + + self->EventCallbackCommand->SetAbortFlag(1); + self->StartInteraction(); + self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); + self->Render(); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::MoveAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + // So as to change the cursor shape when the mouse is poised over + // the widget. Unfortunately, this results in a few extra picks + // due to the cell picker. However given that its picking simple geometry + // like the handles/arrows, this should be very quick + int X = self->Interactor->GetEventPosition()[0]; + int Y = self->Interactor->GetEventPosition()[1]; + int changed = 0; + + if (self->ManagesCursor && self->WidgetState != vtkDiskWidget::Active) + { + int oldInteractionState = + reinterpret_cast(self->WidgetRep)->GetInteractionState(); + + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(vtkDiskRepresentation::Moving); + int state = self->WidgetRep->ComputeInteractionState(X, Y); + changed = self->UpdateCursorShape(state); + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(oldInteractionState); + changed = (changed || state != oldInteractionState) ? 1 : 0; + } + + // See whether we're active + if (self->WidgetState == vtkDiskWidget::Start) + { + if (changed && self->ManagesCursor) + { + self->Render(); + } + return; + } + + // Okay, adjust the representation + double e[2]; + e[0] = static_cast(X); + e[1] = static_cast(Y); + self->WidgetRep->WidgetInteraction(e); + + // moving something + self->EventCallbackCommand->SetAbortFlag(1); + self->InvokeEvent(vtkCommand::InteractionEvent, nullptr); + self->Render(); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::EndSelectAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + if ( + self->WidgetState != vtkDiskWidget::Active || + self->WidgetRep->GetInteractionState() == vtkDiskRepresentation::Outside) + { + return; + } + + // Return state to not selected + double e[2]; + self->WidgetRep->EndWidgetInteraction(e); + self->WidgetState = vtkDiskWidget::Start; + self->ReleaseFocus(); + + // Update cursor if managed + self->UpdateCursorShape( + reinterpret_cast(self->WidgetRep)->GetRepresentationState()); + + self->EventCallbackCommand->SetAbortFlag(1); + self->EndInteraction(); + self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr); + self->Render(); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::MoveDiskAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + reinterpret_cast(self->WidgetRep) + ->SetInteractionState(vtkDiskRepresentation::Moving); + + int X = self->Interactor->GetEventPosition()[0]; + int Y = self->Interactor->GetEventPosition()[1]; + self->WidgetRep->ComputeInteractionState(X, Y); + + // The cursor must be over part of the widget for these key presses to work + if (self->WidgetRep->GetInteractionState() == vtkDiskRepresentation::Outside) + { + return; + } + + // Invoke all of the events associated with moving the cylinder + self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); + + // Move the cylinder + double factor = (self->Interactor->GetControlKey() ? 0.5 : 1.0); + if ( + vtkStdString(self->Interactor->GetKeySym()) == vtkStdString("Down") || + vtkStdString(self->Interactor->GetKeySym()) == vtkStdString("Left")) + { + self->GetDiskRepresentation()->BumpDisk(-1, factor); + } + else + { + self->GetDiskRepresentation()->BumpDisk(1, factor); + } + self->InvokeEvent(vtkCommand::InteractionEvent, nullptr); + + self->EventCallbackCommand->SetAbortFlag(1); + self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr); + self->Render(); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::PickNormalAction(vtkAbstractWidget* w) +{ + vtkDiskWidget* self = reinterpret_cast(w); + + int X = self->Interactor->GetEventPosition()[0]; + int Y = self->Interactor->GetEventPosition()[1]; + + // Invoke all the events associated with moving the plane + self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); + bool newNormalPicked = + self->GetDiskRepresentation()->PickNormal(X, Y, self->Interactor->GetControlKey() == 1); + self->InvokeEvent(vtkCommand::InteractionEvent, nullptr); + self->EventCallbackCommand->SetAbortFlag(1); + self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr); + if (newNormalPicked) + { + self->Render(); + } +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::SetEnabled(int enabling) +{ + if (this->Enabled == enabling) + { + return; + } + + Superclass::SetEnabled(enabling); +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::CreateDefaultRepresentation() +{ + if (!this->WidgetRep) + { + this->WidgetRep = vtkDiskRepresentation::New(); + } +} + +//---------------------------------------------------------------------- +void vtkDiskWidget::SetRepresentation(vtkDiskRepresentation* rep) +{ + this->Superclass::SetWidgetRepresentation(reinterpret_cast(rep)); +} + +//---------------------------------------------------------------------- +int vtkDiskWidget::UpdateCursorShape(int state) +{ + // So as to change the cursor shape when the mouse is poised over + // the widget. + if (this->ManagesCursor) + { + if (state == vtkDiskRepresentation::Outside) + { + return this->RequestCursorShape(VTK_CURSOR_DEFAULT); + } + else if (state == vtkDiskRepresentation::AdjustingRadius) + { + return this->RequestCursorShape(VTK_CURSOR_SIZEALL); + } + else + { + return this->RequestCursorShape(VTK_CURSOR_HAND); + } + } + + return 0; +} + +//---------------------------------------------------------------------------- +void vtkDiskWidget::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/smtk/extension/vtk/widgets/vtkDiskWidget.h b/smtk/extension/vtk/widgets/vtkDiskWidget.h new file mode 100644 index 0000000000..b1a6c91518 --- /dev/null +++ b/smtk/extension/vtk/widgets/vtkDiskWidget.h @@ -0,0 +1,162 @@ +//========================================================================= +// 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 vtkDiskWidget_h +#define vtkDiskWidget_h + +#include "smtk/extension/vtk/widgets/vtkSMTKWidgetsExtModule.h" // For export macro +#include "vtkAbstractWidget.h" + +class vtkDiskRepresentation; + +/** + * @class vtkDiskWidget + * @brief 3D widget for manipulating an infinite cylinder + * + * This 3D widget defines a planar, circular disk that can be + * interactively placed in a scene. The widget is assumed to consist + * of four parts: 1) a disk contained in a 2) bounding box, with a + * 3) normal vector, which is rooted at a 4) center point in the bounding + * box. (The representation paired with this widget determines the + * actual geometry of the widget.) + * + * To use this widget, you generally pair it with a vtkDiskRepresentation + * (or a subclass). Various options are available for controlling how the + * representation appears, and how the widget functions. + * + * @par Event Bindings: + * By default, the widget responds to the following VTK events (i.e., it + * watches the vtkRenderWindowInteractor for these events): + *
    + * If the normal vector is selected:
    + *   LeftButtonPressEvent - select normal
    + *   LeftButtonReleaseEvent - release (end select) normal
    + *   MouseMoveEvent - orient the normal vector
    + * If the center point (handle) is selected:
    + *   LeftButtonPressEvent - select handle (if on slider)
    + *   LeftButtonReleaseEvent - release handle (if selected)
    + *   MouseMoveEvent - move the center point (constrained to plane or on the
    + *                    axis if CTRL key is pressed)
    + * If the disk is selected:
    + *   LeftButtonPressEvent - select disk
    + *   LeftButtonReleaseEvent - release disk
    + *   MouseMoveEvent - increase/decrease disk radius
    + * If the outline is selected:
    + *   LeftButtonPressEvent - select outline
    + *   LeftButtonReleaseEvent - release outline
    + *   MouseMoveEvent - move the outline
    + * If the keypress characters are used
    + *   'Down/Left' Move disk away from viewer
    + *   'Up/Right' Move disk towards viewer
    + * In all the cases, independent of what is picked, the widget responds to the
    + * following VTK events:
    + *   MiddleButtonPressEvent - move the disk
    + *   MiddleButtonReleaseEvent - release the disk
    + *   RightButtonPressEvent - scale the widget's representation
    + *   RightButtonReleaseEvent - stop scaling the widget
    + *   MouseMoveEvent - scale (if right button) or move (if middle button) the widget
    + * 
    + * + * @par Event Bindings: + * Note that the event bindings described above can be changed using this + * class's vtkWidgetEventTranslator. This class translates VTK events + * into the vtkDiskWidget's widget events: + *
    + *   vtkWidgetEvent::Select -- some part of the widget has been selected
    + *   vtkWidgetEvent::EndSelect -- the selection process has completed
    + *   vtkWidgetEvent::Move -- a request for widget motion has been invoked
    + *   vtkWidgetEvent::Up and vtkWidgetEvent::Down -- MoveConeAction
    + * 
    + * + * @par Event Bindings: + * In turn, when these widget events are processed, the vtkDiskWidget + * invokes the following VTK events on itself (which observers can listen for): + *
    + *   vtkCommand::StartInteractionEvent (on vtkWidgetEvent::Select)
    + *   vtkCommand::EndInteractionEvent (on vtkWidgetEvent::EndSelect)
    + *   vtkCommand::InteractionEvent (on vtkWidgetEvent::Move)
    + * 
    + * + * + * @sa + * vtk3DWidget vtkImplicitPlaneWidget +*/ +class VTKSMTKWIDGETSEXT_EXPORT vtkDiskWidget : public vtkAbstractWidget +{ +public: + /** + * Instantiate the object. + */ + static vtkDiskWidget* New(); + + //@{ + /** + * Standard vtkObject methods + */ + vtkTypeMacro(vtkDiskWidget, vtkAbstractWidget); + void PrintSelf(ostream& os, vtkIndent indent) override; + //@} + + vtkDiskWidget(const vtkDiskWidget&) = delete; + vtkDiskWidget& operator=(const vtkDiskWidget&) = delete; + + /** + * Specify an instance of vtkWidgetRepresentation used to represent this + * widget in the scene. Note that the representation is a subclass of vtkProp + * so it can be added to the renderer independent of the widget. + */ + void SetRepresentation(vtkDiskRepresentation* rep); + + /// Control widget interactivity, allowing users to interact with the camera or other widgets. + /// + /// The camera is unobserved when the widget is disabled. + void SetEnabled(int enabling) override; + + /** + * Return the representation as a vtkDiskRepresentation. + */ + vtkDiskRepresentation* GetDiskRepresentation() + { + return reinterpret_cast(this->WidgetRep); + } + + /** + * Create the default widget representation if one is not set. + */ + void CreateDefaultRepresentation() override; + +protected: + vtkDiskWidget(); + ~vtkDiskWidget() override; + + // Manage the state of the widget + int WidgetState; + enum _WidgetState + { + Start = 0, + Active + }; + + // These methods handle events + static void SelectAction(vtkAbstractWidget*); + static void TranslateAction(vtkAbstractWidget*); + static void ScaleAction(vtkAbstractWidget*); + static void EndSelectAction(vtkAbstractWidget*); + static void MoveAction(vtkAbstractWidget*); + static void MoveDiskAction(vtkAbstractWidget*); + static void PickNormalAction(vtkAbstractWidget*); + + /** + * Update the cursor shape based on the interaction state. Returns 1 + * if the cursor shape requested is different from the existing one. + */ + int UpdateCursorShape(int interactionState); +}; + +#endif -- GitLab From b5f00acab5f1a18803bb968caceb0855e40f65da Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 27 Mar 2025 18:42:16 -0400 Subject: [PATCH 24/41] Fix an issue where the same worklet was added multiple times. --- smtk/extension/qt/qtWorkletModel.cxx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/smtk/extension/qt/qtWorkletModel.cxx b/smtk/extension/qt/qtWorkletModel.cxx index e89a43bf7d..245ee18081 100644 --- a/smtk/extension/qt/qtWorkletModel.cxx +++ b/smtk/extension/qt/qtWorkletModel.cxx @@ -283,12 +283,18 @@ void qtWorkletModel::workletUpdate( // components. So use a set to assemble all of the uniquely created worklets and then // insert them all at the same time. std::set workletsToBeInserted; + std::set allWorklets(m_worklets.begin(), m_worklets.end()); smtk::resource::Component::Visitor visitor = - [this, workletTypeName, &workletsToBeInserted](const smtk::resource::Component::Ptr& comp) { + [this, workletTypeName, &allWorklets, &workletsToBeInserted]( + const smtk::resource::Component::Ptr& comp) { if (comp->matchesType(workletTypeName)) { auto worklet = std::dynamic_pointer_cast(comp); - m_worklets.push_back(worklet); + if (allWorklets.find(worklet) == allWorklets.end()) + { + m_worklets.push_back(worklet); + allWorklets.insert(worklet); + } // Lets see if the worklet should be presented? if (m_parentTask) { @@ -324,7 +330,11 @@ void qtWorkletModel::workletUpdate( { if (auto worklet = std::dynamic_pointer_cast(comp)) { - m_worklets.push_back(worklet); + if (allWorklets.find(worklet) == allWorklets.end()) + { + m_worklets.push_back(worklet); + allWorklets.insert(worklet); + } // Lets see if the worklet should be presented? if (m_parentTask) { -- GitLab From 8168630016535a58925af4c9bdc0c9173c22cfb8 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 27 Mar 2025 19:28:10 -0400 Subject: [PATCH 25/41] Introduce `smtk::common::URL` and `smtk::resource::Artifact`. --- smtk/common/CMakeLists.txt | 2 + smtk/common/URL.cxx | 187 ++++++++++++++++++++++ smtk/common/URL.h | 80 +++++++++ smtk/common/pybind11/PybindCommon.cxx | 2 + smtk/common/pybind11/PybindURL.h | 51 ++++++ smtk/common/testing/python/CMakeLists.txt | 1 + smtk/common/testing/python/testURL.py | 40 +++++ smtk/resource/Artifact.cxx | 60 +++++++ smtk/resource/Artifact.h | 121 ++++++++++++++ smtk/resource/CMakeLists.txt | 2 + 10 files changed, 546 insertions(+) create mode 100644 smtk/common/URL.cxx create mode 100644 smtk/common/URL.h create mode 100644 smtk/common/pybind11/PybindURL.h create mode 100644 smtk/common/testing/python/testURL.py create mode 100644 smtk/resource/Artifact.cxx create mode 100644 smtk/resource/Artifact.h diff --git a/smtk/common/CMakeLists.txt b/smtk/common/CMakeLists.txt index 2c5489637c..132b5029b9 100644 --- a/smtk/common/CMakeLists.txt +++ b/smtk/common/CMakeLists.txt @@ -17,6 +17,7 @@ set(commonSrcs TimeZone.cxx timezonespec.cxx TypeContainer.cxx + URL.cxx UUID.cxx UUIDGenerator.cxx VersionNumber.cxx @@ -63,6 +64,7 @@ set(commonHeaders TypeName.h TypeContainer.h TypeTraits.h + URL.h UUID.h UUIDGenerator.h VersionNumber.h diff --git a/smtk/common/URL.cxx b/smtk/common/URL.cxx new file mode 100644 index 0000000000..da665fac79 --- /dev/null +++ b/smtk/common/URL.cxx @@ -0,0 +1,187 @@ +//========================================================================= +// 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/URL.h" + +#include "tao/pegtl.hpp" +#include "tao/pegtl/contrib/uri.hpp" + +namespace pegtl = tao::pegtl; + +namespace smtk +{ +namespace common +{ +namespace uri +{ + +template +struct bind +{ + template + static void apply(const Input& in, smtk::common::URL& url) + { + (url.*Field)(smtk::string::Token(in.string())); + } +}; + +// clang-format off +template< typename Rule > struct action {}; + +template<> struct action< pegtl::uri::scheme > : bind< &URL::setScheme > {}; +template<> struct action< pegtl::uri::authority > : bind< &URL::setAuthority > {}; +// template<> struct action< pegtl::uri::host > : bind< &URL::setHost > {}; +// template<> struct action< pegtl::uri::port > : bind< &URL::setPort > {}; +template<> struct action< pegtl::uri::path_noscheme > : bind< &URL::setPath > {}; +template<> struct action< pegtl::uri::path_rootless > : bind< &URL::setPath > {}; +template<> struct action< pegtl::uri::path_absolute > : bind< &URL::setPath > {}; +template<> struct action< pegtl::uri::path_abempty > : bind< &URL::setPath > {}; +template<> struct action< pegtl::uri::query > : bind< &URL::setQuery > {}; +template<> struct action< pegtl::uri::fragment > : bind< &URL::setFragment > {}; +// clang-format on + +} // namespace uri + +URL::URL(const std::string& txt) +{ + using grammar = pegtl::must; + pegtl::memory_input<> input(txt, "smtk::common::URL"); + pegtl::parse(input, *this); +} + +URL::URL(Token scheme, Token authority, Token path, Token query, Token fragment) + : m_scheme(scheme) + , m_authority(authority) + , m_path(path) + , m_query(query) + , m_fragment(fragment) +{ +} + +bool URL::valid() const +{ + // The URL "/" is not valid. If you want the root of the local filesystem, use "file:///". + return m_path.valid() || m_scheme.valid(); +} + +bool URL::setScheme(Token scheme) +{ + if (scheme == m_scheme) + { + return false; + } + m_scheme = scheme; + return true; +} + +bool URL::setAuthority(Token authority) +{ + if (authority == m_authority) + { + return false; + } + m_authority = authority; + return true; +} + +bool URL::setPath(Token path) +{ + if (path == m_path) + { + return false; + } + m_path = path; + return true; +} + +bool URL::setQuery(Token query) +{ + if (query == m_query) + { + return false; + } + m_query = query; + return true; +} + +bool URL::setFragment(Token fragment) +{ + if (fragment == m_fragment) + { + return false; + } + m_fragment = fragment; + return true; +} + +bool URL::operator!=(URL const& other) const +{ + return m_scheme != other.m_scheme || m_authority != other.m_authority || m_path != other.m_path || + m_query != other.m_query || m_fragment != other.m_fragment; +} + +bool URL::operator==(URL const& other) const +{ + return m_scheme == other.m_scheme && m_authority == other.m_authority && m_path == other.m_path && + m_query == other.m_query && m_fragment == other.m_fragment; +} + +bool URL::operator<(URL const& other) const +{ + return m_scheme < other.m_scheme || + (m_scheme == other.m_scheme && + (m_authority < other.m_authority || + (m_authority == other.m_authority && + (m_path < other.m_path || + (m_path == other.m_path && + (m_query < other.m_query || + (m_query == other.m_query && m_fragment < other.m_fragment))))))); +} + +URL::operator bool() const +{ + return this->valid(); +} + +URL::operator std::string() const +{ + std::string result; + if (m_scheme.valid()) + { + result += m_scheme.data() + ":"; + } + if (m_authority.valid()) + { + result += "//" + m_authority.data(); + } + // result += "/"; + if (m_path.valid()) + { + result += m_path.data(); + } + if (m_query.valid()) + { + result += "?" + m_query.data(); + } + if (m_fragment.valid()) + { + result += "#" + m_fragment.data(); + } + return result; +} + +std::ostream& operator<<(std::ostream& stream, const URL& url) +{ + std::string data = (std::string)url; + stream << data; + return stream; +} + +} // namespace common +} // namespace smtk diff --git a/smtk/common/URL.h b/smtk/common/URL.h new file mode 100644 index 0000000000..c1513a08ef --- /dev/null +++ b/smtk/common/URL.h @@ -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. +//========================================================================= +#ifndef smtk_common_URL_h +#define smtk_common_URL_h + +#include "smtk/string/Token.h" + +namespace smtk +{ +namespace common +{ + +/** An RFC3986-compliant Uniform Resource Locator (URL). + * + * This class holds URL data as a set of string tokens. + * If given a single string, that string is parsed and + * decomposed into tokens for querying data. + */ +class SMTKCORE_EXPORT URL +{ +public: + using Token = smtk::string::Token; + + URL() = default; + URL(const URL& other) = default; + URL(const std::string& txt); + URL(Token scheme, Token authority, Token path, Token query = Token(), Token fragment = Token()); + + bool valid() const; + bool operator!=(URL const& other) const; + bool operator==(URL const& other) const; + bool operator<(URL const& other) const; + + URL& operator=(URL const& other) = default; + + operator bool() const; + explicit operator std::string() const; + + ///@{ + /// Set/get various elements of the URL. + /// + /// See [rfc3896](https://www.rfc-editor.org/rfc/rfc3986) for a detailed + /// description of each element in a URL. + Token scheme() const { return m_scheme; } + bool setScheme(Token scheme); + + Token authority() const { return m_authority; } + bool setAuthority(Token authority); + + Token path() const { return m_path; } + bool setPath(Token path); + + Token query() const { return m_query; } + bool setQuery(Token query); + + Token fragment() const { return m_fragment; } + bool setFragment(Token fragment); + ///@} + +protected: + Token m_scheme; + Token m_authority; + Token m_path; + Token m_query; + Token m_fragment; +}; + +SMTKCORE_EXPORT std::ostream& operator<<(std::ostream& stream, const URL& url); + +} // namespace common +} // namespace smtk + +#endif // smtk_common_URL_h diff --git a/smtk/common/pybind11/PybindCommon.cxx b/smtk/common/pybind11/PybindCommon.cxx index a26b1e806d..8f8ffb9150 100644 --- a/smtk/common/pybind11/PybindCommon.cxx +++ b/smtk/common/pybind11/PybindCommon.cxx @@ -42,6 +42,7 @@ using PySharedPtrClass = py::class_, Args...>; #include "PybindRangeDetector.h" #include "PybindStringUtil.h" #include "PybindTimeZone.h" +#include "PybindURL.h" #include "PybindUUID.h" #include "PybindUUIDGenerator.h" #include "PybindUnionFind.h" @@ -73,6 +74,7 @@ PYBIND11_MODULE(_smtkPybindCommon, common) #endif py::class_< smtk::common::StringUtil > smtk_common_StringUtil = pybind11_init_smtk_common_StringUtil(common); py::class_< smtk::common::TimeZone > smtk_common_TimeZone = pybind11_init_smtk_common_TimeZone(common); + py::class_< smtk::common::URL > smtk_common_URL = pybind11_init_smtk_common_URL(common); py::class_< smtk::common::UUID > smtk_common_UUID = pybind11_init_smtk_common_UUID(common); py::class_< smtk::common::UUIDGenerator > smtk_common_UUIDGenerator = pybind11_init_smtk_common_UUIDGenerator(common); py::class_< smtk::common::Version > smtk_common_Version = pybind11_init_smtk_common_Version(common); diff --git a/smtk/common/pybind11/PybindURL.h b/smtk/common/pybind11/PybindURL.h new file mode 100644 index 0000000000..7c567f25af --- /dev/null +++ b/smtk/common/pybind11/PybindURL.h @@ -0,0 +1,51 @@ +//========================================================================= +// 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_common_URL_h +#define pybind_smtk_common_URL_h + +#include + +#include "smtk/common/URL.h" + +namespace py = pybind11; + +inline py::class_< smtk::common::URL > pybind11_init_smtk_common_URL(py::module &m) +{ + py::class_< smtk::common::URL > instance(m, "URL"); + instance + .def(py::init<>()) + .def(py::init<::smtk::common::URL const &>()) + .def(py::init<::std::string const &>()) + .def("deepcopy", + (smtk::common::URL & (smtk::common::URL::*)(::smtk::common::URL const &)) + &smtk::common::URL::operator=) + .def("valid", &smtk::common::URL::valid) + .def("to_string", [](const smtk::common::URL& url) + { + auto result = (std::string)url; + return result; + } + ) + .def("scheme", (smtk::string::Token (smtk::common::URL::*)() const)&smtk::common::URL::scheme) + .def("authority", (smtk::string::Token (smtk::common::URL::*)() const)&smtk::common::URL::authority) + .def("path", (smtk::string::Token (smtk::common::URL::*)() const)&smtk::common::URL::path) + .def("query", (smtk::string::Token (smtk::common::URL::*)() const)&smtk::common::URL::query) + .def("fragment", (smtk::string::Token (smtk::common::URL::*)() const)&smtk::common::URL::fragment) + .def("setScheme", [](smtk::common::URL* url, const std::string& txt) { return url->setScheme(txt); }, py::arg("scheme")) + .def("setAuthority", [](smtk::common::URL* url, const std::string& txt) { return url->setAuthority(txt); }, py::arg("authority")) + .def("setPath", [](smtk::common::URL* url, const std::string& txt) { return url->setPath(txt); }, py::arg("path")) + .def("setQuery", [](smtk::common::URL* url, const std::string& txt) { return url->setQuery(txt); }, py::arg("query")) + .def("setFragment", [](smtk::common::URL* url, const std::string& txt) { return url->setFragment(txt); }, py::arg("fragment")) + ; + return instance; +} + +#endif diff --git a/smtk/common/testing/python/CMakeLists.txt b/smtk/common/testing/python/CMakeLists.txt index 65a8ab094d..8b08400cd0 100644 --- a/smtk/common/testing/python/CMakeLists.txt +++ b/smtk/common/testing/python/CMakeLists.txt @@ -1,6 +1,7 @@ set(smtkCommonPythonTests uuidGenerator datetimezonepairtest + testURL ) if (ParaView_VERSION VERSION_GREATER_EQUAL "5.10.0") diff --git a/smtk/common/testing/python/testURL.py b/smtk/common/testing/python/testURL.py new file mode 100644 index 0000000000..33273caee3 --- /dev/null +++ b/smtk/common/testing/python/testURL.py @@ -0,0 +1,40 @@ +# ============================================================================= +# +# 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 os +import sys +import unittest +import smtk +import smtk.string +import smtk.common +from smtk import common +import smtk.testing +import uuid + + +class TestURL(unittest.TestCase): + + def test(self): + blank = smtk.common.URL() + self.assertFalse( + blank.valid(), 'Default-constructed URL should be invalid.') + for txt in ('https://kitware.com/foo/bar', 'file:///foo/bar', 'file://baz/foo/bar', 'http://xyzzy@baz/foo/bar?bleb#flim'): + parsed = smtk.common.URL(txt) + print('"', parsed.scheme().data(), '", "', parsed.authority().data(), '", "', parsed.path( + ).data(), '", "', parsed.query().data(), '", "', parsed.fragment().data(), '"') + print(parsed.to_string(), txt == parsed.to_string()) + self.assertEqual(parsed.to_string(), txt, + 'Round-tripped URL was not identical') + + +if __name__ == '__main__': + smtk.testing.process_arguments() + unittest.main() diff --git a/smtk/resource/Artifact.cxx b/smtk/resource/Artifact.cxx new file mode 100644 index 0000000000..bee50b7b6e --- /dev/null +++ b/smtk/resource/Artifact.cxx @@ -0,0 +1,60 @@ +//========================================================================= +// 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/resource/Artifact.h" + +namespace smtk +{ +namespace resource +{ + +bool Artifact::setLocation(URL location) +{ + if (m_location == location) + { + return false; + } + m_location = location; + return true; +} + +bool Artifact::setChecksum(smtk::string::Token algorithm, const std::vector& value) +{ + if (m_checksumAlgorithm == algorithm && m_checksumData == value) + { + return false; + } + m_checksumAlgorithm = algorithm; + m_checksumData = value; + return true; +} + +bool Artifact::setTimestamp(smtk::string::Token format, const std::vector& value) +{ + if (m_timestampFormat == format && m_timestampData == value) + { + return false; + } + m_timestampFormat = format; + m_timestampData = value; + return true; +} + +bool Artifact::setExtant(bool isExtant) +{ + if (m_extant == isExtant) + { + return false; + } + m_extant = isExtant; + return true; +} + +} // namespace resource +} // namespace smtk diff --git a/smtk/resource/Artifact.h b/smtk/resource/Artifact.h new file mode 100644 index 0000000000..cba22c7860 --- /dev/null +++ b/smtk/resource/Artifact.h @@ -0,0 +1,121 @@ +//========================================================================= +// 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_resource_Artifact_h +#define smtk_resource_Artifact_h + +#include "smtk/common/URL.h" // For ivar and API. +#include "smtk/resource/Component.h" + +namespace smtk +{ +namespace resource +{ + +/**\brief An artifact is a component that represents external data. + * + * Artifacts are typically used to represent files that hold opaque + * data relevant to a simulation. Examples include historical input + * decks, job logs, job results files, meshes of simulation domains. + * + * Artifacts may be stored inside or outside of a resource (i.e., they + * may be shared among multiple resources or they may be considered + * owned by a single resource). + * + * Artifacts may be small or large. + * + * What characterizes artifacts is that they reference data external + * to a resource. The data is referenced via an smtk::common::URL. + * URLs may be absolute (and must be if the data is stored external to + * the resource) or relative (and should be if the data is stored internal + * to the resource). Any relative URL is considered relative to its parent + * resource's location. + * + * Artifacts may also be characterized by a checksum and a timestamp. + * This allows resources to determine whether an artifact has been + * modified since the last time it was accessed. + * + * Note that this class is abstract; if you wish your resource to store + * instances of Artifact, you will need to subclass it to provide + * + */ +class SMTKCORE_EXPORT Artifact : public PersistentObject +{ +public: + using URL = smtk::common::URL; + + smtkTypeMacro(smtk::resource::Artifact); + smtkSuperclassMacro(smtk::resource::Component); + smtkSharedFromThisMacro(smtk::resource::PersistentObject); + + ~Artifact() override = default; + + // const smtk::common::UUID& id() const override { return m_id; } + // void setId(const smtk::common::UUID& uid) override { m_id = uid; } + + /// Return the location of the artifact's data. + const URL& location() const { return m_location; } + + /// Set the location of the artifact's data. + bool setLocation(URL location); + + /// Indicate whether the artifact has a checksum provided by returning + /// the checksum algorithm. + /// + /// If the returned token is invalid, no checksum is available. + smtk::string::Token hasChecksum() const { return m_checksumAlgorithm; } + + /// Return the artifact's checksum (if it has one). + std::vector checksumData() const { return m_checksumData; } + + /// Set the artifact's current checksum. + virtual bool setChecksum(smtk::string::Token algorithm, const std::vector& value); + + /// Indicate whether the artifact has a timestamp provided by returning + /// the timestamp format. + /// + /// If the returned token is invalid, no timestamp is available. + smtk::string::Token hasTimestamp() const { return m_timestampFormat; } + + /// Return the artifact's timestamp (if it has one). + std::vector timestampData() const { return m_timestampData; } + + /// Set the artifact's current timestamp. + virtual bool setTimestamp(smtk::string::Token format, const std::vector& value); + + /// Return true if the artifact is still extant and false if expired. + /// + /// If an application determines an artifact is no longer accessible, + /// it may call setExtant(). By default, artifacts are extant upon + /// creation. + bool extant() const { return m_extant; } + + /// Set whether an artifact exists at its location or not. + /// + /// Artifacts may not be extant initially (for example a log file may not + /// exist before a simulation job has commenced) or finally (for example a + /// log file may be removed after a certain time) or at other times in + /// the lifecycle of the artifact. + bool setExtant(bool isExtant); + +protected: + Artifact(); + + smtk::common::UUID m_id; + URL m_location; + smtk::string::Token m_checksumAlgorithm; + std::vector m_checksumData; + smtk::string::Token m_timestampFormat; + std::vector m_timestampData; + bool m_extant{ true }; +}; +} // namespace resource +} // namespace smtk + +#endif // smtk_resource_Artifact_h diff --git a/smtk/resource/CMakeLists.txt b/smtk/resource/CMakeLists.txt index ea3e64e756..82e0a32ed9 100644 --- a/smtk/resource/CMakeLists.txt +++ b/smtk/resource/CMakeLists.txt @@ -1,5 +1,6 @@ # set up sources to build set(resourceSrcs + Artifact.cxx Component.cxx ComponentLinks.cxx CopyOptions.cxx @@ -26,6 +27,7 @@ set(resourceSrcs ) set(resourceHeaders + Artifact.h Component.h ComponentLinks.h Container.h -- GitLab From 2c11502f4503589575ea38297496ae4d436f0aaf Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 28 Mar 2025 14:49:26 -0400 Subject: [PATCH 26/41] Improved python bindings for basic SMTK types. --- smtk/common/pybind11/PybindURL.h | 7 +++ smtk/common/pybind11/PybindUUID.h | 2 + smtk/common/testing/python/testURL.py | 28 +++++++++-- smtk/string/pybind11/PybindToken.h | 18 ++++++- smtk/string/testing/CMakeLists.txt | 6 +-- smtk/string/testing/python/CMakeLists.txt | 7 +++ smtk/string/testing/python/testStringToken.py | 50 +++++++++++++++++++ 7 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 smtk/string/testing/python/CMakeLists.txt create mode 100644 smtk/string/testing/python/testStringToken.py diff --git a/smtk/common/pybind11/PybindURL.h b/smtk/common/pybind11/PybindURL.h index 7c567f25af..48e93c88ed 100644 --- a/smtk/common/pybind11/PybindURL.h +++ b/smtk/common/pybind11/PybindURL.h @@ -24,6 +24,13 @@ inline py::class_< smtk::common::URL > pybind11_init_smtk_common_URL(py::module .def(py::init<>()) .def(py::init<::smtk::common::URL const &>()) .def(py::init<::std::string const &>()) + .def("__eq__", [](smtk::common::URL* url, smtk::common::URL* other) + { return *url == *other; }, py::arg("other")) + .def("__lt__", [](smtk::common::URL* url, smtk::common::URL* other) + { return *url < *other; }, py::arg("other")) + .def("__hash__", [](smtk::common::URL* url) { return smtk::string::Token((std::string)*url).id(); }) + .def("__str__", [](smtk::common::URL* url) { return (std::string)*url; }) + .def("__repr__", [](smtk::common::URL* url) { return "URL('" + (std::string)*url + "')"; }) .def("deepcopy", (smtk::common::URL & (smtk::common::URL::*)(::smtk::common::URL const &)) &smtk::common::URL::operator=) diff --git a/smtk/common/pybind11/PybindUUID.h b/smtk/common/pybind11/PybindUUID.h index a3c0c968b7..36bff5a284 100644 --- a/smtk/common/pybind11/PybindUUID.h +++ b/smtk/common/pybind11/PybindUUID.h @@ -31,6 +31,8 @@ inline py::class_< smtk::common::UUID > pybind11_init_smtk_common_UUID(py::modul .def("__ne__", (bool (smtk::common::UUID::*)(::smtk::common::UUID const &) const) &smtk::common::UUID::operator!=) .def("__eq__", (bool (smtk::common::UUID::*)(::smtk::common::UUID const &) const) &smtk::common::UUID::operator==) .def("__lt__", (bool (smtk::common::UUID::*)(::smtk::common::UUID const &) const) &smtk::common::UUID::operator<) + .def("__hash__", [](const smtk::common::UUID& uid) { return uid.hash(); }) + .def("__repr__", [](const smtk::common::UUID& uid) { return "UUID('" + uid.toString() + "')"; }) .def("deepcopy", (smtk::common::UUID & (smtk::common::UUID::*)(::smtk::common::UUID const &)) &smtk::common::UUID::operator=) .def_static("random", &smtk::common::UUID::random) .def_static("null", &smtk::common::UUID::null) diff --git a/smtk/common/testing/python/testURL.py b/smtk/common/testing/python/testURL.py index 33273caee3..581ce8fccd 100644 --- a/smtk/common/testing/python/testURL.py +++ b/smtk/common/testing/python/testURL.py @@ -26,13 +26,33 @@ class TestURL(unittest.TestCase): blank = smtk.common.URL() self.assertFalse( blank.valid(), 'Default-constructed URL should be invalid.') - for txt in ('https://kitware.com/foo/bar', 'file:///foo/bar', 'file://baz/foo/bar', 'http://xyzzy@baz/foo/bar?bleb#flim'): + urls = ('https://kitware.com/foo/bar', + 'file:///foo/bar', + 'file://baz/foo/bar', + 'http://xyzzy@baz/foo/bar?bleb', + 'http://xyzzy@baz/foo/bar?bleb#flim') + allParsed = [] + for txt in urls: parsed = smtk.common.URL(txt) - print('"', parsed.scheme().data(), '", "', parsed.authority().data(), '", "', parsed.path( - ).data(), '", "', parsed.query().data(), '", "', parsed.fragment().data(), '"') + print('"', parsed.scheme().data(), + '", "', parsed.authority().data(), + '", "', parsed.path().data(), + '", "', parsed.query().data(), + '", "', parsed.fragment().data(), + '"') print(parsed.to_string(), txt == parsed.to_string()) self.assertEqual(parsed.to_string(), txt, - 'Round-tripped URL was not identical') + 'Round-tripped URL was not identical.') + allParsed.append(parsed) + self.assertEqual(len(allParsed), len( + urls), 'Expected to parse every URL.') + self.assertEqual(allParsed, [smtk.common.URL(x) for x in urls], + 'Same test in should produce identical URLs out.') + s1 = set(allParsed) + s2 = set([smtk.common.URL(x) for x in urls]) + print(s1) + self.assertEqual( + s1, s2, 'Hashing should produce identical sets of URLs.') if __name__ == '__main__': diff --git a/smtk/string/pybind11/PybindToken.h b/smtk/string/pybind11/PybindToken.h index 0dad97c200..c973046491 100644 --- a/smtk/string/pybind11/PybindToken.h +++ b/smtk/string/pybind11/PybindToken.h @@ -22,13 +22,25 @@ inline py::class_ pybind11_init_smtk_string_Token(py::modul py::class_ instance(m, "Token"); instance .def_static("manager", &smtk::string::Token::manager) + .def(py::init<>()) .def(py::init()) .def(py::init()) .def("data", &smtk::string::Token::data) .def("id", &smtk::string::Token::id) + .def("valid", &smtk::string::Token::valid) + .def("__str__", [](const smtk::string::Token& token) + { return token.data(); }) .def("__repr__", [](const smtk::string::Token& token) { - return ""; + if (token.valid()) + { + if (token.hasData()) + { + return "Token('" + token.data() + "')"; + } + return "Token(" + std::to_string(token.id()) + ")"; + } + return std::string("Token()"); }) .def("__hash__", [](const smtk::string::Token& self) { @@ -38,6 +50,10 @@ inline py::class_ pybind11_init_smtk_string_Token(py::modul { return self == other; }) + .def("__lt__", [](const smtk::string::Token& self, const smtk::string::Token& other) + { + return self < other; + }) ; return instance; } diff --git a/smtk/string/testing/CMakeLists.txt b/smtk/string/testing/CMakeLists.txt index 2941d367a3..83305da3a2 100644 --- a/smtk/string/testing/CMakeLists.txt +++ b/smtk/string/testing/CMakeLists.txt @@ -1,5 +1,5 @@ add_subdirectory(cxx) -# if(SMTK_ENABLE_PYTHON_WRAPPING) -# add_subdirectory(python) -# endif() +if(SMTK_ENABLE_PYTHON_WRAPPING) + add_subdirectory(python) +endif() diff --git a/smtk/string/testing/python/CMakeLists.txt b/smtk/string/testing/python/CMakeLists.txt new file mode 100644 index 0000000000..0525061eb4 --- /dev/null +++ b/smtk/string/testing/python/CMakeLists.txt @@ -0,0 +1,7 @@ +set(smtkStringPythonTests + testStringToken +) + +foreach (test ${smtkStringPythonTests}) + smtk_add_test_python(${test}Py ${test}.py --src-dir=${smtk_SOURCE_DIR}) +endforeach() diff --git a/smtk/string/testing/python/testStringToken.py b/smtk/string/testing/python/testStringToken.py new file mode 100644 index 0000000000..b8e05684af --- /dev/null +++ b/smtk/string/testing/python/testStringToken.py @@ -0,0 +1,50 @@ +# ============================================================================= +# +# 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 os +import sys +import unittest +import smtk +import smtk.string +import smtk.common +from smtk import common +import smtk.testing + + +class TestStringToken(unittest.TestCase): + + def test(self): + blank = smtk.string.Token() + print(blank) + self.assertFalse( + blank.valid(), 'Default-constructed Token should be invalid.') + texts = ('a', 'b', 'c', 'aa') + allParsed = [] + for txt in texts: + parsed = smtk.string.Token(txt) + print(parsed) + self.assertEqual(str(parsed), txt, + 'Round-tripped Token was not identical.') + allParsed.append(parsed) + self.assertEqual(len(allParsed), len(texts), + 'Expected to parse every Token.') + self.assertEqual(allParsed, [smtk.string.Token(x) for x in texts], + 'Same text in should produce identical Tokens out.') + s1 = set(allParsed) + s2 = set([smtk.string.Token(x) for x in texts]) + print(s1) + self.assertEqual( + s1, s2, 'Hashing should produce identical sets of Tokens.') + + +if __name__ == '__main__': + smtk.testing.process_arguments() + unittest.main() -- GitLab From 79b25600df0a86062c9bed63b269a83b4170ee5f Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sat, 29 Mar 2025 17:35:17 -0400 Subject: [PATCH 27/41] Fix an issue where attributes were not displayed. A project with no tasks present would not have any of its attribute resources displayed; the attribute panel was assuming that any attribute resource belonging to a project would use task-system styles to display resources but not all projects provide this. --- .../paraview/appcomponents/pqSMTKAttributePanel.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx b/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx index ead06a6013..c09b09f2f0 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx @@ -505,7 +505,10 @@ bool pqSMTKAttributePanel::displayResourceInternal( { // Only use the top level view if the resource is not // managed by a project - if (rsrc && (rsrc->parentResource() == nullptr)) + auto* project = dynamic_cast(rsrc->parentResource()); + if ( + rsrc && + (!project || (project && project->taskManager().taskInstances().topLevelTasks().empty()))) { theView = rsrc->findTopLevelView(); } -- GitLab From 935ef255a847d59daf02e4f48072453f392c4ede Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 8 Apr 2025 22:29:16 -0400 Subject: [PATCH 28/41] Provide handler functions for operation instances. You can now have a handler function called once an operation completes (as if it were an observer but only on the instance of the operation you add it to, and only for one invocation). This also removes the wrapping of many copy-constructors and assignment operations in python because (a) they never should have been added and (b) the mutex guarding the container of handlers does not provide a default copy constructor which now prohibits them from existing. --- doc/release/notes/operation-handlers.rst | 32 +++++ doc/userguide/operation/operators.rst | 31 +++++ doc/userguide/operation/support.rst | 2 + smtk/attribute/pybind11/PybindAssociate.h | 2 - smtk/attribute/pybind11/PybindDissociate.h | 2 - smtk/attribute/pybind11/PybindExport.h | 2 - smtk/attribute/pybind11/PybindImport.h | 2 - smtk/attribute/pybind11/PybindRead.h | 2 - smtk/attribute/pybind11/PybindWrite.h | 2 - smtk/mesh/pybind11/PybindDeleteMesh.h | 1 - smtk/mesh/pybind11/PybindElevateMesh.h | 2 - smtk/mesh/pybind11/PybindExport.h | 2 - smtk/mesh/pybind11/PybindImport.h | 2 - .../mesh/pybind11/PybindInterpolateOntoMesh.h | 2 - smtk/mesh/pybind11/PybindRead.h | 2 - smtk/mesh/pybind11/PybindWrite.h | 2 - .../pybind11/PybindAddAuxiliaryGeometry.h | 2 - smtk/model/pybind11/PybindCloseModel.h | 2 - smtk/model/pybind11/PybindCreateInstances.h | 2 - smtk/model/pybind11/PybindDelete.h | 2 - smtk/model/pybind11/PybindExportModelJSON.h | 2 - smtk/operation/Operation.cxx | 43 +++++++ smtk/operation/Operation.h | 43 ++++++- smtk/operation/pybind11/PybindOperation.h | 4 +- smtk/operation/pybind11/PybindReadResource.h | 2 - .../operation/pybind11/PybindRemoveResource.h | 2 - smtk/operation/pybind11/PybindSetProperty.h | 2 - smtk/operation/pybind11/PybindWriteResource.h | 2 - smtk/operation/pybind11/PybindXMLOperation.h | 1 - smtk/operation/testing/python/CMakeLists.txt | 6 + .../testing/python/testOperationHandler.py | 119 ++++++++++++++++++ smtk/project/pybind11/PybindOperation.h | 1 - .../mesh/pybind11/PybindCreateUniformGrid.h | 2 - .../pybind11/PybindEulerCharacteristicRatio.h | 2 - smtk/session/mesh/pybind11/PybindExport.h | 2 - smtk/session/mesh/pybind11/PybindImport.h | 2 - smtk/session/mesh/pybind11/PybindRead.h | 2 - smtk/session/mesh/pybind11/PybindWrite.h | 2 - .../polygon/pybind11/PybindCreateEdge.h | 1 - .../pybind11/PybindCreateEdgeFromPoints.h | 2 - .../pybind11/PybindCreateEdgeFromVertices.h | 2 - .../polygon/pybind11/PybindCreateFaces.h | 2 - .../polygon/pybind11/PybindCreateModel.h | 1 - .../polygon/pybind11/PybindCreateVertices.h | 2 - smtk/session/polygon/pybind11/PybindDelete.h | 2 - .../polygon/pybind11/PybindDemoteVertex.h | 2 - .../polygon/pybind11/PybindExtractContours.h | 2 - .../polygon/pybind11/PybindForceCreateFace.h | 2 - smtk/session/polygon/pybind11/PybindImport.h | 2 - .../polygon/pybind11/PybindImportPPG.h | 2 - .../polygon/pybind11/PybindLegacyRead.h | 2 - .../polygon/pybind11/PybindOperation.h | 3 - smtk/session/polygon/pybind11/PybindRead.h | 2 - .../polygon/pybind11/PybindSplitEdge.h | 2 - .../polygon/pybind11/PybindTweakEdge.h | 2 - smtk/session/polygon/pybind11/PybindWrite.h | 2 - smtk/session/vtk/pybind11/PybindExport.h | 1 - smtk/session/vtk/pybind11/PybindImport.h | 1 - smtk/session/vtk/pybind11/PybindLegacyRead.h | 1 - smtk/session/vtk/pybind11/PybindOperation.h | 3 - smtk/session/vtk/pybind11/PybindRead.h | 1 - smtk/session/vtk/pybind11/PybindWrite.h | 1 - 62 files changed, 274 insertions(+), 106 deletions(-) create mode 100644 doc/release/notes/operation-handlers.rst create mode 100644 smtk/operation/testing/python/testOperationHandler.py diff --git a/doc/release/notes/operation-handlers.rst b/doc/release/notes/operation-handlers.rst new file mode 100644 index 0000000000..758d14a9a6 --- /dev/null +++ b/doc/release/notes/operation-handlers.rst @@ -0,0 +1,32 @@ +Operation System +================ + +Handlers for specific operation instances +----------------------------------------- + +You may now add and remove handlers to an instance of an operation. +A handler is a function to be invoked upon the next completion of +one instance of an operation object (regardless of whether the result +indicates success or failure). + +Handlers are function objects with a signature similar to observers +(see :ref:`operation-observers`) +except that they are only called upon completion of an operation: +handlers may not cancel the operation and are not passed an ``EventType`` +(only the operation and its result). +Handlers, like observers, are invoked at a time when all the resource +locks required for the operation are held. + +Handlers are invoked on the thread in which the operation is run (unlike +observers, which are invoked on the main/GUI thread in Qt applications). + +Handlers are invoked only for the instance of the operation they are +added to; if you create multiple operations of the same type and add +a handler to one, only that instance will have the handler called. + +Handlers are invoked zero or one times at most. +Operation handlers are removed each time the operation is invoked; +you are responsible for adding handlers for each invocation. +It is acceptable for a handler to add itself to the operation which +invoked it (as the container of handlers is copied and cleared before +any handlers are invoked). diff --git a/doc/userguide/operation/operators.rst b/doc/userguide/operation/operators.rst index 98a63f0ddd..3d6beb6259 100644 --- a/doc/userguide/operation/operators.rst +++ b/doc/userguide/operation/operators.rst @@ -60,3 +60,34 @@ means that one need not construct an instance of the operator's C++ class in ord to obtain information about it; instead, simply call :smtk:`operatorSystem() ` on the session and ask for all the definitions which inherit "operator". + +Handlers +-------- + +You may add and remove handlers to an instance of an operation. +A handler is a function to be invoked upon the next completion of +one instance of an operation object (regardless of whether the result +indicates success or failure). + +Handlers are function objects with a signature similar to observers +except that they are only called upon completion of an operation: +handlers may not cancel the operation and are not passed an ``EventType`` +(only the operation and its result). +Handlers, like observers, are invoked at a time when all the resource +locks required for the operation are held. + +Handlers are invoked on the thread in which the operation is run (unlike +observers, which are invoked on the main/GUI thread in Qt applications). + +Handlers are invoked only for the instance of the operation they are +added to; if you create multiple operations of the same type and add +a handler to one, only that instance will have the handler called. + +Handlers are invoked zero or one times at most. +Operation handlers are removed each time the operation is invoked; +you are responsible for adding handlers for each invocation. +If an operation is canceled by an observer, the handler is removed +from the operation and not invoked. +It is acceptable for a handler to add itself to the operation which +invoked it (as the container of handlers is copied and cleared before +any handlers are invoked). diff --git a/doc/userguide/operation/support.rst b/doc/userguide/operation/support.rst index 0cef96f932..668bd75218 100644 --- a/doc/userguide/operation/support.rst +++ b/doc/userguide/operation/support.rst @@ -188,6 +188,8 @@ as may operations that only require read access to the same resource. However, operations that require write access to the same resource will be run sequentially. +.. _operation-observers: + Observing operations -------------------- diff --git a/smtk/attribute/pybind11/PybindAssociate.h b/smtk/attribute/pybind11/PybindAssociate.h index d87c54fc7f..5ce9cfa50c 100644 --- a/smtk/attribute/pybind11/PybindAssociate.h +++ b/smtk/attribute/pybind11/PybindAssociate.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::attribute::Associate, smtk::operation::XMLOperati PySharedPtrClass< smtk::attribute::Associate, smtk::operation::XMLOperation > instance(m, "Associate"); instance .def(py::init<>()) - .def(py::init<::smtk::attribute::Associate const &>()) - .def("deepcopy", (smtk::attribute::Associate & (smtk::attribute::Associate::*)(::smtk::attribute::Associate const &)) &smtk::attribute::Associate::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::attribute::Associate::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::attribute::Associate::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::attribute::Associate::*)() const) &smtk::attribute::Associate::shared_from_this) diff --git a/smtk/attribute/pybind11/PybindDissociate.h b/smtk/attribute/pybind11/PybindDissociate.h index a9d928807c..d587cc83ea 100644 --- a/smtk/attribute/pybind11/PybindDissociate.h +++ b/smtk/attribute/pybind11/PybindDissociate.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::attribute::Dissociate, smtk::operation::XMLOperat PySharedPtrClass< smtk::attribute::Dissociate, smtk::operation::XMLOperation > instance(m, "Dissociate"); instance .def(py::init<>()) - .def(py::init<::smtk::attribute::Dissociate const &>()) - .def("deepcopy", (smtk::attribute::Dissociate & (smtk::attribute::Dissociate::*)(::smtk::attribute::Dissociate const &)) &smtk::attribute::Dissociate::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::attribute::Dissociate::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::attribute::Dissociate::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::attribute::Dissociate::*)() const) &smtk::attribute::Dissociate::shared_from_this) diff --git a/smtk/attribute/pybind11/PybindExport.h b/smtk/attribute/pybind11/PybindExport.h index a20bb348f9..49431b4de7 100644 --- a/smtk/attribute/pybind11/PybindExport.h +++ b/smtk/attribute/pybind11/PybindExport.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::attribute::Export, smtk::operation::XMLOperation PySharedPtrClass< smtk::attribute::Export, smtk::operation::XMLOperation > instance(m, "Export"); instance .def(py::init<>()) - .def(py::init<::smtk::attribute::Export const &>()) - .def("deepcopy", (smtk::attribute::Export & (smtk::attribute::Export::*)(::smtk::attribute::Export const &)) &smtk::attribute::Export::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::attribute::Export::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::attribute::Export::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::attribute::Export::*)() const) &smtk::attribute::Export::shared_from_this) diff --git a/smtk/attribute/pybind11/PybindImport.h b/smtk/attribute/pybind11/PybindImport.h index 2c6d16ad18..9b4d38eb7f 100644 --- a/smtk/attribute/pybind11/PybindImport.h +++ b/smtk/attribute/pybind11/PybindImport.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::attribute::Import, smtk::operation::XMLOperation PySharedPtrClass< smtk::attribute::Import, smtk::operation::XMLOperation > instance(m, "Import"); instance .def(py::init<>()) - .def(py::init<::smtk::attribute::Import const &>()) - .def("deepcopy", (smtk::attribute::Import & (smtk::attribute::Import::*)(::smtk::attribute::Import const &)) &smtk::attribute::Import::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::attribute::Import::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::attribute::Import::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::attribute::Import::*)() const) &smtk::attribute::Import::shared_from_this) diff --git a/smtk/attribute/pybind11/PybindRead.h b/smtk/attribute/pybind11/PybindRead.h index e72a26a73a..45c1b3ba30 100644 --- a/smtk/attribute/pybind11/PybindRead.h +++ b/smtk/attribute/pybind11/PybindRead.h @@ -25,8 +25,6 @@ inline PySharedPtrClass< smtk::attribute::Read, smtk::operation::XMLOperation > PySharedPtrClass< smtk::attribute::Read, smtk::operation::XMLOperation > instance(m, "Read"); instance .def(py::init<>()) - .def(py::init<::smtk::attribute::Read const &>()) - .def("deepcopy", (smtk::attribute::Read & (smtk::attribute::Read::*)(::smtk::attribute::Read const &)) &smtk::attribute::Read::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::attribute::Read::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::attribute::Read::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::attribute::Read::*)() const) &smtk::attribute::Read::shared_from_this) diff --git a/smtk/attribute/pybind11/PybindWrite.h b/smtk/attribute/pybind11/PybindWrite.h index b7a3b08347..22cd6bd27a 100644 --- a/smtk/attribute/pybind11/PybindWrite.h +++ b/smtk/attribute/pybind11/PybindWrite.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::attribute::Write, smtk::operation::XMLOperation > PySharedPtrClass< smtk::attribute::Write, smtk::operation::XMLOperation > instance(m, "Write"); instance .def(py::init<>()) - .def(py::init<::smtk::attribute::Write const &>()) - .def("deepcopy", (smtk::attribute::Write & (smtk::attribute::Write::*)(::smtk::attribute::Write const &)) &smtk::attribute::Write::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::attribute::Write::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::attribute::Write::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::attribute::Write::*)() const) &smtk::attribute::Write::shared_from_this) diff --git a/smtk/mesh/pybind11/PybindDeleteMesh.h b/smtk/mesh/pybind11/PybindDeleteMesh.h index be40074620..8623beca0b 100644 --- a/smtk/mesh/pybind11/PybindDeleteMesh.h +++ b/smtk/mesh/pybind11/PybindDeleteMesh.h @@ -23,7 +23,6 @@ inline PySharedPtrClass< smtk::mesh::DeleteMesh, smtk::operation::XMLOperation > { PySharedPtrClass< smtk::mesh::DeleteMesh, smtk::operation::XMLOperation > instance(m, "DeleteMesh"); instance - .def("deepcopy", (smtk::mesh::DeleteMesh & (smtk::mesh::DeleteMesh::*)(::smtk::mesh::DeleteMesh const &)) &smtk::mesh::DeleteMesh::operator=) .def("ableToOperate", &smtk::mesh::DeleteMesh::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::DeleteMesh::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::DeleteMesh::create, py::arg("ref")) diff --git a/smtk/mesh/pybind11/PybindElevateMesh.h b/smtk/mesh/pybind11/PybindElevateMesh.h index df4662f721..e8548545d0 100644 --- a/smtk/mesh/pybind11/PybindElevateMesh.h +++ b/smtk/mesh/pybind11/PybindElevateMesh.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::mesh::ElevateMesh, smtk::operation::XMLOperation PySharedPtrClass< smtk::mesh::ElevateMesh, smtk::operation::XMLOperation > instance(m, "ElevateMesh"); instance .def(py::init<>()) - .def(py::init<::smtk::mesh::ElevateMesh const &>()) - .def("deepcopy", (smtk::mesh::ElevateMesh & (smtk::mesh::ElevateMesh::*)(::smtk::mesh::ElevateMesh const &)) &smtk::mesh::ElevateMesh::operator=) .def("ableToOperate", &smtk::mesh::ElevateMesh::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::ElevateMesh::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::ElevateMesh::create, py::arg("ref")) diff --git a/smtk/mesh/pybind11/PybindExport.h b/smtk/mesh/pybind11/PybindExport.h index 11a2a8e8b2..c9d7cde222 100644 --- a/smtk/mesh/pybind11/PybindExport.h +++ b/smtk/mesh/pybind11/PybindExport.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::mesh::Export, smtk::operation::XMLOperation > pyb PySharedPtrClass< smtk::mesh::Export, smtk::operation::XMLOperation > instance(m, "Export"); instance .def(py::init<>()) - .def(py::init<::smtk::mesh::Export const &>()) - .def("deepcopy", (smtk::mesh::Export & (smtk::mesh::Export::*)(::smtk::mesh::Export const &)) &smtk::mesh::Export::operator=) .def("ableToOperate", &smtk::mesh::Export::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::Export::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::Export::create, py::arg("ref")) diff --git a/smtk/mesh/pybind11/PybindImport.h b/smtk/mesh/pybind11/PybindImport.h index 2e54d72de5..2fe8a437ac 100644 --- a/smtk/mesh/pybind11/PybindImport.h +++ b/smtk/mesh/pybind11/PybindImport.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::mesh::Import, smtk::operation::XMLOperation > pyb PySharedPtrClass< smtk::mesh::Import, smtk::operation::XMLOperation > instance(m, "Import"); instance .def(py::init<>()) - .def(py::init<::smtk::mesh::Import const &>()) - .def("deepcopy", (smtk::mesh::Import & (smtk::mesh::Import::*)(::smtk::mesh::Import const &)) &smtk::mesh::Import::operator=) .def("ableToOperate", &smtk::mesh::Import::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::Import::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::Import::create, py::arg("ref")) diff --git a/smtk/mesh/pybind11/PybindInterpolateOntoMesh.h b/smtk/mesh/pybind11/PybindInterpolateOntoMesh.h index c00203f0cf..e6374ac9b9 100644 --- a/smtk/mesh/pybind11/PybindInterpolateOntoMesh.h +++ b/smtk/mesh/pybind11/PybindInterpolateOntoMesh.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::mesh::InterpolateOntoMesh, smtk::operation::XMLOp PySharedPtrClass< smtk::mesh::InterpolateOntoMesh, smtk::operation::XMLOperation > instance(m, "InterpolateOntoMesh"); instance .def(py::init<>()) - .def(py::init<::smtk::mesh::InterpolateOntoMesh const &>()) - .def("deepcopy", (smtk::mesh::InterpolateOntoMesh & (smtk::mesh::InterpolateOntoMesh::*)(::smtk::mesh::InterpolateOntoMesh const &)) &smtk::mesh::InterpolateOntoMesh::operator=) .def("ableToOperate", &smtk::mesh::InterpolateOntoMesh::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::InterpolateOntoMesh::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::InterpolateOntoMesh::create, py::arg("ref")) diff --git a/smtk/mesh/pybind11/PybindRead.h b/smtk/mesh/pybind11/PybindRead.h index cd402f3f16..55054d38d3 100644 --- a/smtk/mesh/pybind11/PybindRead.h +++ b/smtk/mesh/pybind11/PybindRead.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::mesh::Read, smtk::operation::XMLOperation > pybin PySharedPtrClass< smtk::mesh::Read, smtk::operation::XMLOperation > instance(m, "Read"); instance .def(py::init<>()) - .def(py::init<::smtk::mesh::Read const &>()) - .def("deepcopy", (smtk::mesh::Read & (smtk::mesh::Read::*)(::smtk::mesh::Read const &)) &smtk::mesh::Read::operator=) .def("ableToOperate", &smtk::mesh::Read::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::Read::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::Read::create, py::arg("ref")) diff --git a/smtk/mesh/pybind11/PybindWrite.h b/smtk/mesh/pybind11/PybindWrite.h index 83c6259021..cfde610355 100644 --- a/smtk/mesh/pybind11/PybindWrite.h +++ b/smtk/mesh/pybind11/PybindWrite.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::mesh::Write, smtk::operation::XMLOperation > pybi PySharedPtrClass< smtk::mesh::Write, smtk::operation::XMLOperation > instance(m, "Write"); instance .def(py::init<>()) - .def(py::init<::smtk::mesh::Write const &>()) - .def("deepcopy", (smtk::mesh::Write & (smtk::mesh::Write::*)(::smtk::mesh::Write const &)) &smtk::mesh::Write::operator=) .def("ableToOperate", &smtk::mesh::Write::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::mesh::Write::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::mesh::Write::create, py::arg("ref")) diff --git a/smtk/model/pybind11/PybindAddAuxiliaryGeometry.h b/smtk/model/pybind11/PybindAddAuxiliaryGeometry.h index 35c0d26f8b..2c7519b3f8 100644 --- a/smtk/model/pybind11/PybindAddAuxiliaryGeometry.h +++ b/smtk/model/pybind11/PybindAddAuxiliaryGeometry.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::model::AddAuxiliaryGeometry, smtk::operation::XML PySharedPtrClass< smtk::model::AddAuxiliaryGeometry, smtk::operation::XMLOperation > instance(m, "AddAuxiliaryGeometry"); instance .def(py::init<>()) - .def(py::init<::smtk::model::AddAuxiliaryGeometry const &>()) - .def("deepcopy", (smtk::model::AddAuxiliaryGeometry & (smtk::model::AddAuxiliaryGeometry::*)(::smtk::model::AddAuxiliaryGeometry const &)) &smtk::model::AddAuxiliaryGeometry::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::model::AddAuxiliaryGeometry::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::model::AddAuxiliaryGeometry::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::model::AddAuxiliaryGeometry::*)() const) &smtk::model::AddAuxiliaryGeometry::shared_from_this) diff --git a/smtk/model/pybind11/PybindCloseModel.h b/smtk/model/pybind11/PybindCloseModel.h index 7167f61c1b..5fe80ad919 100644 --- a/smtk/model/pybind11/PybindCloseModel.h +++ b/smtk/model/pybind11/PybindCloseModel.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::model::CloseModel, smtk::operation::XMLOperation PySharedPtrClass< smtk::model::CloseModel, smtk::operation::XMLOperation > instance(m, "CloseModel"); instance .def(py::init<>()) - .def(py::init<::smtk::model::CloseModel const &>()) - .def("deepcopy", (smtk::model::CloseModel & (smtk::model::CloseModel::*)(::smtk::model::CloseModel const &)) &smtk::model::CloseModel::operator=) .def("ableToOperate", &smtk::model::CloseModel::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::model::CloseModel::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::model::CloseModel::create, py::arg("ref")) diff --git a/smtk/model/pybind11/PybindCreateInstances.h b/smtk/model/pybind11/PybindCreateInstances.h index 9a5a132911..decbb5e9ab 100644 --- a/smtk/model/pybind11/PybindCreateInstances.h +++ b/smtk/model/pybind11/PybindCreateInstances.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::model::CreateInstances, smtk::operation::XMLOpera PySharedPtrClass< smtk::model::CreateInstances, smtk::operation::XMLOperation > instance(m, "CreateInstances"); instance .def(py::init<>()) - .def(py::init<::smtk::model::CreateInstances const &>()) - .def("deepcopy", (smtk::model::CreateInstances & (smtk::model::CreateInstances::*)(::smtk::model::CreateInstances const &)) &smtk::model::CreateInstances::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::model::CreateInstances::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::model::CreateInstances::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::model::CreateInstances::*)() const) &smtk::model::CreateInstances::shared_from_this) diff --git a/smtk/model/pybind11/PybindDelete.h b/smtk/model/pybind11/PybindDelete.h index 21922018b3..620aef924a 100644 --- a/smtk/model/pybind11/PybindDelete.h +++ b/smtk/model/pybind11/PybindDelete.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::model::Delete, smtk::operation::XMLOperation > py PySharedPtrClass< smtk::model::Delete, smtk::operation::XMLOperation > instance(m, "Delete"); instance .def(py::init<>()) - .def(py::init<::smtk::model::Delete const &>()) - .def("deepcopy", (smtk::model::Delete & (smtk::model::Delete::*)(::smtk::model::Delete const &)) &smtk::model::Delete::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::model::Delete::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::model::Delete::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::model::Delete::*)() const) &smtk::model::Delete::shared_from_this) diff --git a/smtk/model/pybind11/PybindExportModelJSON.h b/smtk/model/pybind11/PybindExportModelJSON.h index 87fafcd78d..72a08fc51e 100644 --- a/smtk/model/pybind11/PybindExportModelJSON.h +++ b/smtk/model/pybind11/PybindExportModelJSON.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::model::ExportModelJSON, smtk::operation::XMLOpera PySharedPtrClass< smtk::model::ExportModelJSON, smtk::operation::XMLOperation > instance(m, "ExportModelJSON"); instance .def(py::init<>()) - .def(py::init<::smtk::model::ExportModelJSON const &>()) - .def("deepcopy", (smtk::model::ExportModelJSON & (smtk::model::ExportModelJSON::*)(::smtk::model::ExportModelJSON const &)) &smtk::model::ExportModelJSON::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::model::ExportModelJSON::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::model::ExportModelJSON::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::model::ExportModelJSON::*)() const) &smtk::model::ExportModelJSON::shared_from_this) diff --git a/smtk/operation/Operation.cxx b/smtk/operation/Operation.cxx index 0f4d6dcb7c..01c3f2c585 100644 --- a/smtk/operation/Operation.cxx +++ b/smtk/operation/Operation.cxx @@ -139,6 +139,13 @@ Operation::Result Operation::operate() Operation::Result Operation::operate(const BaseKey& key) { + std::multimap instanceHandlers; + { + std::lock_guard guard(m_handlerLock); + instanceHandlers = m_handlers; + m_handlers.clear(); + } + // Gather all requested resources and their lock types. const auto resourcesAndLockTypes = this->identifyLocksRequired(); @@ -373,6 +380,14 @@ 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) + { + entry.second(*this, result); + } + instanceHandlers.clear(); if (key.m_observerOption == ObserverOption::InvokeObservers && observePostOperation && manager) { manager->observers()(*this, EventType::DID_OPERATE, result); @@ -529,6 +544,34 @@ Operation::Result Operation::createResult(Outcome outcome) return result; } +void Operation::addHandler(Handler handler, Priority priority) +{ + std::lock_guard guard(m_handlerLock); + m_handlers.emplace(priority, handler); +} + +bool Operation::removeHandler(Handler handler, Priority priority) +{ + std::lock_guard guard(m_handlerLock); + for (auto it = m_handlers.lower_bound(priority); it != m_handlers.upper_bound(priority); ++it) + { + if (it->second.target() == handler.target()) + { + m_handlers.erase(it); + return true; + } + } + return false; +} + +bool Operation::clearHandlers() +{ + std::lock_guard guard(m_handlerLock); + bool didRemove = !m_handlers.empty(); + m_handlers.clear(); + return didRemove; +} + void Operation::markModifiedResources(Operation::Result& result) { // Gather all requested resources and their lock types. diff --git a/smtk/operation/Operation.h b/smtk/operation/Operation.h index 9dead7e3de..4489886249 100644 --- a/smtk/operation/Operation.h +++ b/smtk/operation/Operation.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -67,17 +68,20 @@ class SMTKCORE_EXPORT Operation : smtkEnableSharedPtr(Operation) public: smtkTypeMacroBase(smtk::operation::Operation); - // A hash value uniquely representing the operation. + /// A priority for Handlers. + using Priority = int; + + /// A hash value uniquely representing the operation. typedef std::size_t Index; - // An attribute describing the operation's input. + /// An attribute describing the operation's input. typedef std::shared_ptr Parameters; - // An attribute containing the operation's result. + /// An attribute containing the operation's result. typedef std::shared_ptr Result; - // An attribute resource containing the operation's execution definition - // result definition. + /// An attribute resource containing the operation's execution definition + /// result definition. typedef std::shared_ptr Specification; typedef std::shared_ptr Definition; @@ -186,6 +190,33 @@ public: /// attribute is distinguished by its derivation from the "result" attribute. Result createResult(Outcome); + /// Pass a \a handler to invoke after the operation's completion (successful or not). + /// The \a handler is called only on this instance of the operation (as opposed to + /// observers of the operation manager, which are invoked for every operation). + /// + /// The \a priority is used to schedule the \a handler relative to other observers. + /// + /// Handlers are cleared each time the operation is run, so if you run the same + /// instance of an operation multiple times, you must add the handler before + /// launching the operation each time. + /// + /// Note that you are expected to avoid launching the same instance of an operation + /// multiple times without ensuring that the operation is not already queued. + /// Otherwise, handlers may be added or removed while a previous instance is running + /// – including removal after the first instance of the operation has completed but + /// before successive instances – making invocation of handlers intermittent after the + /// first operation. + /// + /// A handler may add itself or other handlers back to the operation instance + /// as the operation will copy the handler container before invoking handlers. + void addHandler(Handler handler, Priority priority); + + /// Remove a \a handler from the operation. + bool removeHandler(Handler handler, Priority priority); + + /// Clear all handlers from the operation. + bool clearHandlers(); + /// Operations that are managed have a non-null pointer to their manager. ManagerPtr manager() const { return m_manager.lock(); } @@ -331,6 +362,8 @@ private: Definition m_resultDefinition; std::vector> m_results; ResourceAccessMap m_lockedResources; + std::mutex m_handlerLock; + std::multimap m_handlers; }; /**\brief Return the outcome of an operation given its \a result object. diff --git a/smtk/operation/pybind11/PybindOperation.h b/smtk/operation/pybind11/PybindOperation.h index b9013291ff..5e1775d1e8 100644 --- a/smtk/operation/pybind11/PybindOperation.h +++ b/smtk/operation/pybind11/PybindOperation.h @@ -83,7 +83,6 @@ inline PySharedPtrClass< smtk::operation::Operation, smtk::operation::PyOperatio instance .def(py::init<>()) - .def("deepcopy", (smtk::operation::Operation & (smtk::operation::Operation::*)(::smtk::operation::Operation const &)) &smtk::operation::Operation::operator=) .def_static("create", &smtk::operation::PyOperation::create) .def("typeName", &smtk::operation::Operation::typeName) .def("index", &smtk::operation::Operation::index) @@ -138,6 +137,9 @@ 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("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) .def("runChildOp", [](smtk::operation::Operation* self, const smtk::operation::Operation::Ptr& childOp, smtk::operation::Operation::ObserverOption observerOption, diff --git a/smtk/operation/pybind11/PybindReadResource.h b/smtk/operation/pybind11/PybindReadResource.h index 22e5738101..1ec92d4cf2 100644 --- a/smtk/operation/pybind11/PybindReadResource.h +++ b/smtk/operation/pybind11/PybindReadResource.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::operation::ReadResource, smtk::operation::XMLOper { PySharedPtrClass< smtk::operation::ReadResource, smtk::operation::XMLOperation > instance(m, "ReadResource"); instance - .def(py::init<::smtk::operation::ReadResource const &>()) - .def("deepcopy", (smtk::operation::ReadResource & (smtk::operation::ReadResource::*)(::smtk::operation::ReadResource const &)) &smtk::operation::ReadResource::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::operation::ReadResource::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::operation::ReadResource::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::operation::ReadResource::*)() const) &smtk::operation::ReadResource::shared_from_this) diff --git a/smtk/operation/pybind11/PybindRemoveResource.h b/smtk/operation/pybind11/PybindRemoveResource.h index edb6de9498..13ef374a6f 100644 --- a/smtk/operation/pybind11/PybindRemoveResource.h +++ b/smtk/operation/pybind11/PybindRemoveResource.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::operation::RemoveResource, smtk::operation::XMLOp { PySharedPtrClass< smtk::operation::RemoveResource, smtk::operation::XMLOperation > instance(m, "RemoveResource"); instance - .def(py::init<::smtk::operation::RemoveResource const &>()) - .def("deepcopy", (smtk::operation::RemoveResource & (smtk::operation::RemoveResource::*)(::smtk::operation::RemoveResource const &)) &smtk::operation::RemoveResource::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::operation::RemoveResource::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::operation::RemoveResource::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::operation::RemoveResource::*)() const) &smtk::operation::RemoveResource::shared_from_this) diff --git a/smtk/operation/pybind11/PybindSetProperty.h b/smtk/operation/pybind11/PybindSetProperty.h index 2e0d964431..9aeec35f7a 100644 --- a/smtk/operation/pybind11/PybindSetProperty.h +++ b/smtk/operation/pybind11/PybindSetProperty.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::operation::SetProperty, smtk::operation::XMLOpera PySharedPtrClass< smtk::operation::SetProperty, smtk::operation::XMLOperation > instance(m, "SetProperty"); instance .def(py::init<>()) - .def(py::init<::smtk::operation::SetProperty const &>()) - .def("deepcopy", (smtk::operation::SetProperty & (smtk::operation::SetProperty::*)(::smtk::operation::SetProperty const &)) &smtk::operation::SetProperty::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::operation::SetProperty::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::operation::SetProperty::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::operation::SetProperty::*)() const) &smtk::operation::SetProperty::shared_from_this) diff --git a/smtk/operation/pybind11/PybindWriteResource.h b/smtk/operation/pybind11/PybindWriteResource.h index f54bbefa6b..c679383b94 100644 --- a/smtk/operation/pybind11/PybindWriteResource.h +++ b/smtk/operation/pybind11/PybindWriteResource.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::operation::WriteResource, smtk::operation::XMLOpe { PySharedPtrClass< smtk::operation::WriteResource, smtk::operation::XMLOperation > instance(m, "WriteResource"); instance - .def(py::init<::smtk::operation::WriteResource const &>()) - .def("deepcopy", (smtk::operation::WriteResource & (smtk::operation::WriteResource::*)(::smtk::operation::WriteResource const &)) &smtk::operation::WriteResource::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::operation::WriteResource::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::operation::WriteResource::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::operation::WriteResource::*)() const) &smtk::operation::WriteResource::shared_from_this) diff --git a/smtk/operation/pybind11/PybindXMLOperation.h b/smtk/operation/pybind11/PybindXMLOperation.h index 5bad73ffdc..dce73b0f00 100644 --- a/smtk/operation/pybind11/PybindXMLOperation.h +++ b/smtk/operation/pybind11/PybindXMLOperation.h @@ -23,7 +23,6 @@ inline PySharedPtrClass< smtk::operation::XMLOperation, smtk::operation::Operati { PySharedPtrClass< smtk::operation::XMLOperation, smtk::operation::Operation > instance(m, "XMLOperation"); instance - .def("deepcopy", (smtk::operation::XMLOperation & (smtk::operation::XMLOperation::*)(::smtk::operation::XMLOperation const &)) &smtk::operation::XMLOperation::operator=) .def("shared_from_this", (std::shared_ptr (smtk::operation::XMLOperation::*)() const) &smtk::operation::XMLOperation::shared_from_this) .def("shared_from_this", (std::shared_ptr (smtk::operation::XMLOperation::*)()) &smtk::operation::XMLOperation::shared_from_this) ; diff --git a/smtk/operation/testing/python/CMakeLists.txt b/smtk/operation/testing/python/CMakeLists.txt index 9859534ed8..218eec6174 100644 --- a/smtk/operation/testing/python/CMakeLists.txt +++ b/smtk/operation/testing/python/CMakeLists.txt @@ -6,6 +6,12 @@ set(smtkOperationPythonTests set(smtkOperationPythonDataTests ) +if (SMTK_ENABLE_POLYGON_SESSION) + list(APPEND smtkOperationPythonDataTests + testOperationHandler + ) +endif() + if(SMTK_ENABLE_PARAVIEW_SUPPORT) list(APPEND smtkOperationPythonDataTests testOperationTracing diff --git a/smtk/operation/testing/python/testOperationHandler.py b/smtk/operation/testing/python/testOperationHandler.py new file mode 100644 index 0000000000..aad03641ca --- /dev/null +++ b/smtk/operation/testing/python/testOperationHandler.py @@ -0,0 +1,119 @@ +# ============================================================================= +# +# 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.attribute +import smtk.operation +import smtk.session.polygon +import smtk.testing + +invokedCount = 0 +message = 'test' +addSelf = False + + +def handler(op, result): + global message, invokedCount + invokedCount += 1 + print('%s: Handler invoked (%d)' % (message, invokedCount)) + if addSelf: + op.addHandler(handler, 0) + + +class TestOperationHandler(smtk.testing.TestCase): + + def testSingleHandler(self): + import os + global message, invokedCount + self.mgr = smtk.model.Resource.create() + fpath = [smtk.testing.DATA_DIR, 'model', + '2d', 'smtk', 'epic-trex-drummer.smtk'] + op = smtk.session.polygon.Read.create() + op.parameters().find('filename').setValue(os.path.join(*fpath)) + print + op.addHandler(handler, 0) + op.addHandler(handler, 1) + op.removeHandler(handler, 1) + invokedCount = 0 + message = 'testSingleHandler' + res = op.operate() + if res.find('outcome').value(0) != int(smtk.operation.Operation.SUCCEEDED): + raise RuntimeError + self.assertEqual( + invokedCount, 1, 'Expected to be called exactly once.') + + def testMultipleHandlers(self): + import os + global message, invokedCount + self.mgr = smtk.model.Resource.create() + fpath = [smtk.testing.DATA_DIR, 'model', + '2d', 'smtk', 'epic-trex-drummer.smtk'] + op = smtk.session.polygon.Read.create() + op.parameters().find('filename').setValue(os.path.join(*fpath)) + op.addHandler(handler, 0) + op.addHandler(handler, 1) + invokedCount = 0 + message = 'testMultipleHandlers' + 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 testMultipleTimes(self): + import os + global message, invokedCount, addSelf + self.mgr = smtk.model.Resource.create() + fpath = [smtk.testing.DATA_DIR, 'model', + '2d', 'smtk', 'epic-trex-drummer.smtk'] + op = smtk.session.polygon.Read.create() + op.parameters().find('filename').setValue(os.path.join(*fpath)) + op.addHandler(handler, 0) + op.addHandler(handler, 1) + addSelf = True + invokedCount = 0 + message = 'testMultipleTimes (first time)' + 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.') + + # Invoke the operation a second time. + invokedCount = 0 + message = 'testMultipleTimes (second time)' + # Remove one of the handlers that got re-added since "addSelf" was true. + op.removeHandler(handler, 0) + addSelf = False + res = op.operate() + if res.find('outcome').value(0) != int(smtk.operation.Operation.SUCCEEDED): + raise RuntimeError + self.assertEqual( + invokedCount, 1, 'Expected to be called after first operation.') + + # Invoke the operation a third time (with no re-add of handler). + invokedCount = 0 + message = 'testMultipleTimes (third time)' + addSelf = False + res = op.operate() + if res.find('outcome').value(0) != int(smtk.operation.Operation.SUCCEEDED): + raise RuntimeError + self.assertEqual( + invokedCount, 0, 'Expected to be called no more after the first operation.') + + # Test that no handlers remain on the operation + didRemove = op.clearHandlers() + self.assertFalse(didRemove, 'Expected no handlers remaining.') + + +if __name__ == '__main__': + smtk.testing.process_arguments() + smtk.testing.main() diff --git a/smtk/project/pybind11/PybindOperation.h b/smtk/project/pybind11/PybindOperation.h index 306815c304..8866734156 100644 --- a/smtk/project/pybind11/PybindOperation.h +++ b/smtk/project/pybind11/PybindOperation.h @@ -26,7 +26,6 @@ inline PySharedPtrClass< smtk::project::Operation, smtk::operation::XMLOperation PySharedPtrClass< smtk::project::Operation, smtk::operation::XMLOperation > instance(m, "Operation"); instance - .def("deepcopy", (smtk::project::Operation & (smtk::project::Operation::*)(::smtk::project::Operation const &)) &smtk::project::Operation::operator=) .def("typeName", &smtk::project::Operation::typeName) .def("shared_from_this", (std::shared_ptr (smtk::project::Operation::*)()) &smtk::project::Operation::shared_from_this) .def("shared_from_this", (std::shared_ptr (smtk::project::Operation::*)() const) &smtk::project::Operation::shared_from_this) diff --git a/smtk/session/mesh/pybind11/PybindCreateUniformGrid.h b/smtk/session/mesh/pybind11/PybindCreateUniformGrid.h index 8d307ef0ee..22da73495a 100644 --- a/smtk/session/mesh/pybind11/PybindCreateUniformGrid.h +++ b/smtk/session/mesh/pybind11/PybindCreateUniformGrid.h @@ -23,9 +23,7 @@ inline PySharedPtrClass< smtk::session::mesh::CreateUniformGrid, smtk::operation { PySharedPtrClass< smtk::session::mesh::CreateUniformGrid, smtk::operation::XMLOperation > instance(m, "CreateUniformGrid"); instance - .def(py::init<::smtk::session::mesh::CreateUniformGrid const &>()) .def(py::init<>()) - .def("deepcopy", (smtk::session::mesh::CreateUniformGrid & (smtk::session::mesh::CreateUniformGrid::*)(::smtk::session::mesh::CreateUniformGrid const &)) &smtk::session::mesh::CreateUniformGrid::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::mesh::CreateUniformGrid::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::mesh::CreateUniformGrid::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::mesh::CreateUniformGrid::*)()) &smtk::session::mesh::CreateUniformGrid::shared_from_this) diff --git a/smtk/session/mesh/pybind11/PybindEulerCharacteristicRatio.h b/smtk/session/mesh/pybind11/PybindEulerCharacteristicRatio.h index f138013f3a..c50a531de1 100644 --- a/smtk/session/mesh/pybind11/PybindEulerCharacteristicRatio.h +++ b/smtk/session/mesh/pybind11/PybindEulerCharacteristicRatio.h @@ -23,9 +23,7 @@ inline PySharedPtrClass< smtk::session::mesh::EulerCharacteristicRatio, smtk::op { PySharedPtrClass< smtk::session::mesh::EulerCharacteristicRatio, smtk::operation::XMLOperation > instance(m, "EulerCharacteristicRatio"); instance - .def(py::init<::smtk::session::mesh::EulerCharacteristicRatio const &>()) .def(py::init<>()) - .def("deepcopy", (smtk::session::mesh::EulerCharacteristicRatio & (smtk::session::mesh::EulerCharacteristicRatio::*)(::smtk::session::mesh::EulerCharacteristicRatio const &)) &smtk::session::mesh::EulerCharacteristicRatio::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::mesh::EulerCharacteristicRatio::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::mesh::EulerCharacteristicRatio::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::mesh::EulerCharacteristicRatio::*)()) &smtk::session::mesh::EulerCharacteristicRatio::shared_from_this) diff --git a/smtk/session/mesh/pybind11/PybindExport.h b/smtk/session/mesh/pybind11/PybindExport.h index 631c8e6aeb..a842082da3 100644 --- a/smtk/session/mesh/pybind11/PybindExport.h +++ b/smtk/session/mesh/pybind11/PybindExport.h @@ -23,9 +23,7 @@ inline PySharedPtrClass< smtk::session::mesh::Export, smtk::operation::XMLOperat { PySharedPtrClass< smtk::session::mesh::Export, smtk::operation::XMLOperation > instance(m, "Export"); instance - .def(py::init<::smtk::session::mesh::Export const &>()) .def(py::init<>()) - .def("deepcopy", (smtk::session::mesh::Export & (smtk::session::mesh::Export::*)(::smtk::session::mesh::Export const &)) &smtk::session::mesh::Export::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::mesh::Export::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::mesh::Export::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::mesh::Export::*)()) &smtk::session::mesh::Export::shared_from_this) diff --git a/smtk/session/mesh/pybind11/PybindImport.h b/smtk/session/mesh/pybind11/PybindImport.h index 8c48f8bfaf..7c737a9a54 100644 --- a/smtk/session/mesh/pybind11/PybindImport.h +++ b/smtk/session/mesh/pybind11/PybindImport.h @@ -23,9 +23,7 @@ inline PySharedPtrClass< smtk::session::mesh::Import, smtk::operation::XMLOperat { PySharedPtrClass< smtk::session::mesh::Import, smtk::operation::XMLOperation > instance(m, "Import"); instance - .def(py::init<::smtk::session::mesh::Import const &>()) .def(py::init<>()) - .def("deepcopy", (smtk::session::mesh::Import & (smtk::session::mesh::Import::*)(::smtk::session::mesh::Import const &)) &smtk::session::mesh::Import::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::mesh::Import::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::mesh::Import::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::mesh::Import::*)()) &smtk::session::mesh::Import::shared_from_this) diff --git a/smtk/session/mesh/pybind11/PybindRead.h b/smtk/session/mesh/pybind11/PybindRead.h index c05001a048..6746242067 100644 --- a/smtk/session/mesh/pybind11/PybindRead.h +++ b/smtk/session/mesh/pybind11/PybindRead.h @@ -25,9 +25,7 @@ inline PySharedPtrClass< smtk::session::mesh::Read, smtk::operation::XMLOperatio { PySharedPtrClass< smtk::session::mesh::Read, smtk::operation::XMLOperation > instance(m, "Read"); instance - .def(py::init<::smtk::session::mesh::Read const &>()) .def(py::init<>()) - .def("deepcopy", (smtk::session::mesh::Read & (smtk::session::mesh::Read::*)(::smtk::session::mesh::Read const &)) &smtk::session::mesh::Read::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::mesh::Read::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::mesh::Read::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::mesh::Read::*)()) &smtk::session::mesh::Read::shared_from_this) diff --git a/smtk/session/mesh/pybind11/PybindWrite.h b/smtk/session/mesh/pybind11/PybindWrite.h index ae21f08115..98ffebe4a3 100644 --- a/smtk/session/mesh/pybind11/PybindWrite.h +++ b/smtk/session/mesh/pybind11/PybindWrite.h @@ -25,9 +25,7 @@ inline PySharedPtrClass< smtk::session::mesh::Write, smtk::operation::XMLOperati { PySharedPtrClass< smtk::session::mesh::Write, smtk::operation::XMLOperation > instance(m, "Write"); instance - .def(py::init<::smtk::session::mesh::Write const &>()) .def(py::init<>()) - .def("deepcopy", (smtk::session::mesh::Write & (smtk::session::mesh::Write::*)(::smtk::session::mesh::Write const &)) &smtk::session::mesh::Write::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::mesh::Write::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::mesh::Write::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::mesh::Write::*)()) &smtk::session::mesh::Write::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindCreateEdge.h b/smtk/session/polygon/pybind11/PybindCreateEdge.h index 4e727a8051..4fd8edcb5d 100644 --- a/smtk/session/polygon/pybind11/PybindCreateEdge.h +++ b/smtk/session/polygon/pybind11/PybindCreateEdge.h @@ -21,7 +21,6 @@ inline PySharedPtrClass< smtk::session::polygon::CreateEdge > pybind11_init_smtk { PySharedPtrClass< smtk::session::polygon::CreateEdge > instance(m, "CreateEdge", parent); instance - .def(py::init<::smtk::session::polygon::CreateEdge const &>()) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::CreateEdge::create) ; return instance; diff --git a/smtk/session/polygon/pybind11/PybindCreateEdgeFromPoints.h b/smtk/session/polygon/pybind11/PybindCreateEdgeFromPoints.h index 1935aeaceb..8a33f48850 100644 --- a/smtk/session/polygon/pybind11/PybindCreateEdgeFromPoints.h +++ b/smtk/session/polygon/pybind11/PybindCreateEdgeFromPoints.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::CreateEdgeFromPoints > pybind11 { PySharedPtrClass< smtk::session::polygon::CreateEdgeFromPoints > instance(m, "CreateEdgeFromPoints", parent); instance - .def(py::init<::smtk::session::polygon::CreateEdgeFromPoints const &>()) - .def("deepcopy", (smtk::session::polygon::CreateEdgeFromPoints & (smtk::session::polygon::CreateEdgeFromPoints::*)(::smtk::session::polygon::CreateEdgeFromPoints const &)) &smtk::session::polygon::CreateEdgeFromPoints::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::CreateEdgeFromPoints::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::CreateEdgeFromPoints::create, py::arg("ref")) .def("process", &smtk::session::polygon::CreateEdgeFromPoints::process, py::arg("pnts"), py::arg("numCoordsPerPoint"), py::arg("parentModel")) diff --git a/smtk/session/polygon/pybind11/PybindCreateEdgeFromVertices.h b/smtk/session/polygon/pybind11/PybindCreateEdgeFromVertices.h index 5f293fe1ff..3059e7a68b 100644 --- a/smtk/session/polygon/pybind11/PybindCreateEdgeFromVertices.h +++ b/smtk/session/polygon/pybind11/PybindCreateEdgeFromVertices.h @@ -21,8 +21,6 @@ inline PySharedPtrClass< smtk::session::polygon::CreateEdgeFromVertices > pybind { PySharedPtrClass< smtk::session::polygon::CreateEdgeFromVertices > instance(m, "CreateEdgeFromVertices", parent); instance - .def(py::init<::smtk::session::polygon::CreateEdgeFromVertices const &>()) - .def("deepcopy", (smtk::session::polygon::CreateEdgeFromVertices & (smtk::session::polygon::CreateEdgeFromVertices::*)(::smtk::session::polygon::CreateEdgeFromVertices const &)) &smtk::session::polygon::CreateEdgeFromVertices::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::CreateEdgeFromVertices::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::CreateEdgeFromVertices::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::CreateEdgeFromVertices::*)() const) &smtk::session::polygon::CreateEdgeFromVertices::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindCreateFaces.h b/smtk/session/polygon/pybind11/PybindCreateFaces.h index 0232a87849..83e608559f 100644 --- a/smtk/session/polygon/pybind11/PybindCreateFaces.h +++ b/smtk/session/polygon/pybind11/PybindCreateFaces.h @@ -34,8 +34,6 @@ inline PySharedPtrClass< smtk::session::polygon::CreateFaces > pybind11_init_smt { PySharedPtrClass< smtk::session::polygon::CreateFaces > instance(m, "CreateFaces", parent); instance - .def(py::init<::smtk::session::polygon::CreateFaces const &>()) - .def("deepcopy", (smtk::session::polygon::CreateFaces & (smtk::session::polygon::CreateFaces::*)(::smtk::session::polygon::CreateFaces const &)) &smtk::session::polygon::CreateFaces::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::CreateFaces::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::CreateFaces::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::CreateFaces::*)() const) &smtk::session::polygon::CreateFaces::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindCreateModel.h b/smtk/session/polygon/pybind11/PybindCreateModel.h index c13f00b325..d61613a838 100644 --- a/smtk/session/polygon/pybind11/PybindCreateModel.h +++ b/smtk/session/polygon/pybind11/PybindCreateModel.h @@ -21,7 +21,6 @@ inline PySharedPtrClass< smtk::session::polygon::CreateModel > pybind11_init_smt { PySharedPtrClass< smtk::session::polygon::CreateModel > instance(m, "CreateModel", parent); instance - .def(py::init<::smtk::session::polygon::CreateModel const &>()) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::CreateModel::create) ; return instance; diff --git a/smtk/session/polygon/pybind11/PybindCreateVertices.h b/smtk/session/polygon/pybind11/PybindCreateVertices.h index fe29abbe64..545cda15cb 100644 --- a/smtk/session/polygon/pybind11/PybindCreateVertices.h +++ b/smtk/session/polygon/pybind11/PybindCreateVertices.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::CreateVertices> pybind11_init_s { PySharedPtrClass< smtk::session::polygon::CreateVertices > instance(m, "CreateVertices", parent); instance - .def(py::init<::smtk::session::polygon::CreateVertices const &>()) - .def("deepcopy", (smtk::session::polygon::CreateVertices & (smtk::session::polygon::CreateVertices::*)(::smtk::session::polygon::CreateVertices const &)) &smtk::session::polygon::CreateVertices::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::CreateVertices::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::CreateVertices::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::CreateVertices::*)() const) &smtk::session::polygon::CreateVertices::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindDelete.h b/smtk/session/polygon/pybind11/PybindDelete.h index 68c61196d2..c897b10b9c 100644 --- a/smtk/session/polygon/pybind11/PybindDelete.h +++ b/smtk/session/polygon/pybind11/PybindDelete.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::Delete> pybind11_init_smtk_sess { PySharedPtrClass< smtk::session::polygon::Delete > instance(m, "Delete", parent); instance - .def(py::init<::smtk::session::polygon::Delete const &>()) - .def("deepcopy", (smtk::session::polygon::Delete & (smtk::session::polygon::Delete::*)(::smtk::session::polygon::Delete const &)) &smtk::session::polygon::Delete::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::Delete::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::Delete::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::Delete::*)() const) &smtk::session::polygon::Delete::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindDemoteVertex.h b/smtk/session/polygon/pybind11/PybindDemoteVertex.h index 820101da1e..471408450d 100644 --- a/smtk/session/polygon/pybind11/PybindDemoteVertex.h +++ b/smtk/session/polygon/pybind11/PybindDemoteVertex.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::DemoteVertex > pybind11_init_sm { PySharedPtrClass< smtk::session::polygon::DemoteVertex > instance(m, "DemoteVertex", parent); instance - .def(py::init<::smtk::session::polygon::DemoteVertex const &>()) - .def("deepcopy", (smtk::session::polygon::DemoteVertex & (smtk::session::polygon::DemoteVertex::*)(::smtk::session::polygon::DemoteVertex const &)) &smtk::session::polygon::DemoteVertex::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::DemoteVertex::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::DemoteVertex::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::DemoteVertex::*)() const) &smtk::session::polygon::DemoteVertex::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindExtractContours.h b/smtk/session/polygon/pybind11/PybindExtractContours.h index eec43fff9e..46049721ce 100644 --- a/smtk/session/polygon/pybind11/PybindExtractContours.h +++ b/smtk/session/polygon/pybind11/PybindExtractContours.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::session::polygon::ExtractContours > pybind11_init PySharedPtrClass< smtk::session::polygon::ExtractContours > instance(m, "ExtractContours", parent); instance .def(py::init<>()) - .def(py::init<::smtk::session::polygon::ExtractContours const &>()) - .def("deepcopy", (smtk::session::polygon::ExtractContours & (smtk::session::polygon::ExtractContours::*)(::smtk::session::polygon::ExtractContours const &)) &smtk::session::polygon::ExtractContours::operator=) .def("ableToOperate", &smtk::session::polygon::ExtractContours::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::ExtractContours::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::ExtractContours::create, py::arg("ref")) diff --git a/smtk/session/polygon/pybind11/PybindForceCreateFace.h b/smtk/session/polygon/pybind11/PybindForceCreateFace.h index b380c71098..c625a29112 100644 --- a/smtk/session/polygon/pybind11/PybindForceCreateFace.h +++ b/smtk/session/polygon/pybind11/PybindForceCreateFace.h @@ -24,8 +24,6 @@ inline PySharedPtrClass< smtk::session::polygon::ForceCreateFace > pybind11_init PySharedPtrClass< smtk::session::polygon::ForceCreateFace > instance(m, "ForceCreateFace", parent); instance .def(py::init<>()) - .def(py::init<::smtk::session::polygon::ForceCreateFace const &>()) - .def("deepcopy", (smtk::session::polygon::ForceCreateFace & (smtk::session::polygon::ForceCreateFace::*)(::smtk::session::polygon::ForceCreateFace const &)) &smtk::session::polygon::ForceCreateFace::operator=) .def("ableToOperate", &smtk::session::polygon::ForceCreateFace::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::ForceCreateFace::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::ForceCreateFace::create, py::arg("ref")) diff --git a/smtk/session/polygon/pybind11/PybindImport.h b/smtk/session/polygon/pybind11/PybindImport.h index 2b943c00d4..5b59ab9bae 100644 --- a/smtk/session/polygon/pybind11/PybindImport.h +++ b/smtk/session/polygon/pybind11/PybindImport.h @@ -21,8 +21,6 @@ inline PySharedPtrClass< smtk::session::polygon::Import > pybind11_init_smtk_ses { PySharedPtrClass< smtk::session::polygon::Import > instance(m, "Import", parent); instance - .def(py::init<::smtk::session::polygon::Import const &>()) - .def("deepcopy", (smtk::session::polygon::Import & (smtk::session::polygon::Import::*)(::smtk::session::polygon::Import const &)) &smtk::session::polygon::Import::operator=) .def("ableToOperate", &smtk::session::polygon::Import::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::Import::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::Import::create, py::arg("ref")) diff --git a/smtk/session/polygon/pybind11/PybindImportPPG.h b/smtk/session/polygon/pybind11/PybindImportPPG.h index 4ff34a6652..524e0b9f95 100644 --- a/smtk/session/polygon/pybind11/PybindImportPPG.h +++ b/smtk/session/polygon/pybind11/PybindImportPPG.h @@ -21,8 +21,6 @@ inline PySharedPtrClass< smtk::session::polygon::ImportPPG > pybind11_init_smtk_ { PySharedPtrClass< smtk::session::polygon::ImportPPG > instance(m, "ImportPPG", parent); instance - .def(py::init<::smtk::session::polygon::ImportPPG const &>()) - .def("deepcopy", (smtk::session::polygon::ImportPPG & (smtk::session::polygon::ImportPPG::*)(::smtk::session::polygon::ImportPPG const &)) &smtk::session::polygon::ImportPPG::operator=) .def("ableToOperate", &smtk::session::polygon::ImportPPG::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::ImportPPG::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::ImportPPG::create, py::arg("ref")) diff --git a/smtk/session/polygon/pybind11/PybindLegacyRead.h b/smtk/session/polygon/pybind11/PybindLegacyRead.h index 9850b93a68..e4f346bc08 100644 --- a/smtk/session/polygon/pybind11/PybindLegacyRead.h +++ b/smtk/session/polygon/pybind11/PybindLegacyRead.h @@ -21,8 +21,6 @@ inline PySharedPtrClass< smtk::session::polygon::LegacyRead > pybind11_init_smtk { PySharedPtrClass< smtk::session::polygon::LegacyRead > instance(m, "LegacyRead", parent); instance - .def(py::init<::smtk::session::polygon::LegacyRead const &>()) - .def("deepcopy", (smtk::session::polygon::LegacyRead & (smtk::session::polygon::LegacyRead::*)(::smtk::session::polygon::LegacyRead const &)) &smtk::session::polygon::LegacyRead::operator=) .def("ableToOperate", &smtk::session::polygon::LegacyRead::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::LegacyRead::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::LegacyRead::create, py::arg("ref")) diff --git a/smtk/session/polygon/pybind11/PybindOperation.h b/smtk/session/polygon/pybind11/PybindOperation.h index a5063a4587..4d4e62bd71 100644 --- a/smtk/session/polygon/pybind11/PybindOperation.h +++ b/smtk/session/polygon/pybind11/PybindOperation.h @@ -22,9 +22,6 @@ namespace py = pybind11; inline PySharedPtrClass< smtk::session::polygon::Operation, smtk::operation::XMLOperation > pybind11_init_smtk_session_polygon_Operation(py::module &m) { PySharedPtrClass< smtk::session::polygon::Operation, smtk::operation::XMLOperation > instance(m, "Operation"); - instance - .def("deepcopy", (smtk::session::polygon::Operation & (smtk::session::polygon::Operation::*)(::smtk::session::polygon::Operation const &)) &smtk::session::polygon::Operation::operator=) - ; return instance; } diff --git a/smtk/session/polygon/pybind11/PybindRead.h b/smtk/session/polygon/pybind11/PybindRead.h index cb7de145dc..054a02c8ed 100644 --- a/smtk/session/polygon/pybind11/PybindRead.h +++ b/smtk/session/polygon/pybind11/PybindRead.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::Read > pybind11_init_smtk_sessi { PySharedPtrClass< smtk::session::polygon::Read > instance(m, "Read", parent); instance - .def(py::init<::smtk::session::polygon::Read const &>()) - .def("deepcopy", (smtk::session::polygon::Read & (smtk::session::polygon::Read::*)(::smtk::session::polygon::Read const &)) &smtk::session::polygon::Read::operator=) .def("ableToOperate", &smtk::session::polygon::Read::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::Read::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::Read::create, py::arg("ref")) diff --git a/smtk/session/polygon/pybind11/PybindSplitEdge.h b/smtk/session/polygon/pybind11/PybindSplitEdge.h index 1e6527c1ba..7064e366ad 100644 --- a/smtk/session/polygon/pybind11/PybindSplitEdge.h +++ b/smtk/session/polygon/pybind11/PybindSplitEdge.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::SplitEdge > pybind11_init_smtk_ { PySharedPtrClass< smtk::session::polygon::SplitEdge > instance(m, "SplitEdge", parent); instance - .def(py::init<::smtk::session::polygon::SplitEdge const &>()) - .def("deepcopy", (smtk::session::polygon::SplitEdge & (smtk::session::polygon::SplitEdge::*)(::smtk::session::polygon::SplitEdge const &)) &smtk::session::polygon::SplitEdge::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::SplitEdge::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::SplitEdge::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::SplitEdge::*)() const) &smtk::session::polygon::SplitEdge::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindTweakEdge.h b/smtk/session/polygon/pybind11/PybindTweakEdge.h index ec1467160f..ce89ef4532 100644 --- a/smtk/session/polygon/pybind11/PybindTweakEdge.h +++ b/smtk/session/polygon/pybind11/PybindTweakEdge.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::TweakEdge > pybind11_init_smtk_ { PySharedPtrClass< smtk::session::polygon::TweakEdge > instance(m, "TweakEdge", parent); instance - .def(py::init<::smtk::session::polygon::TweakEdge const &>()) - .def("deepcopy", (smtk::session::polygon::TweakEdge & (smtk::session::polygon::TweakEdge::*)(::smtk::session::polygon::TweakEdge const &)) &smtk::session::polygon::TweakEdge::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::TweakEdge::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::TweakEdge::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::polygon::TweakEdge::*)() const) &smtk::session::polygon::TweakEdge::shared_from_this) diff --git a/smtk/session/polygon/pybind11/PybindWrite.h b/smtk/session/polygon/pybind11/PybindWrite.h index e7bec1512a..a709786dcc 100644 --- a/smtk/session/polygon/pybind11/PybindWrite.h +++ b/smtk/session/polygon/pybind11/PybindWrite.h @@ -23,8 +23,6 @@ inline PySharedPtrClass< smtk::session::polygon::Write > pybind11_init_smtk_sess { PySharedPtrClass< smtk::session::polygon::Write > instance(m, "Write", parent); instance - .def(py::init<::smtk::session::polygon::Write const &>()) - .def("deepcopy", (smtk::session::polygon::Write & (smtk::session::polygon::Write::*)(::smtk::session::polygon::Write const &)) &smtk::session::polygon::Write::operator=) .def("ableToOperate", &smtk::session::polygon::Write::ableToOperate) .def_static("create", (std::shared_ptr (*)()) &smtk::session::polygon::Write::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::polygon::Write::create, py::arg("ref")) diff --git a/smtk/session/vtk/pybind11/PybindExport.h b/smtk/session/vtk/pybind11/PybindExport.h index d33d0104de..df02089f65 100644 --- a/smtk/session/vtk/pybind11/PybindExport.h +++ b/smtk/session/vtk/pybind11/PybindExport.h @@ -22,7 +22,6 @@ inline PySharedPtrClass< smtk::session::vtk::Export > pybind11_init_smtk_session PySharedPtrClass< smtk::session::vtk::Export > instance(m, "Export", parent); instance .def(py::init<>()) - .def("deepcopy", (smtk::session::vtk::Export & (smtk::session::vtk::Export::*)(::smtk::session::vtk::Export const &)) &smtk::session::vtk::Export::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::vtk::Export::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::vtk::Export::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::vtk::Export::*)() const) &smtk::session::vtk::Export::shared_from_this) diff --git a/smtk/session/vtk/pybind11/PybindImport.h b/smtk/session/vtk/pybind11/PybindImport.h index f63fa6a205..34a5121a14 100644 --- a/smtk/session/vtk/pybind11/PybindImport.h +++ b/smtk/session/vtk/pybind11/PybindImport.h @@ -22,7 +22,6 @@ inline PySharedPtrClass< smtk::session::vtk::Import > pybind11_init_smtk_session PySharedPtrClass< smtk::session::vtk::Import > instance(m, "Import", parent); instance .def(py::init<>()) - .def("deepcopy", (smtk::session::vtk::Import & (smtk::session::vtk::Import::*)(::smtk::session::vtk::Import const &)) &smtk::session::vtk::Import::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::vtk::Import::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::vtk::Import::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::vtk::Import::*)() const) &smtk::session::vtk::Import::shared_from_this) diff --git a/smtk/session/vtk/pybind11/PybindLegacyRead.h b/smtk/session/vtk/pybind11/PybindLegacyRead.h index 4e4b24dbb3..3669653441 100644 --- a/smtk/session/vtk/pybind11/PybindLegacyRead.h +++ b/smtk/session/vtk/pybind11/PybindLegacyRead.h @@ -22,7 +22,6 @@ inline PySharedPtrClass< smtk::session::vtk::LegacyRead > pybind11_init_smtk_ses PySharedPtrClass< smtk::session::vtk::LegacyRead > instance(m, "LegacyRead", parent); instance .def(py::init<>()) - .def("deepcopy", (smtk::session::vtk::LegacyRead & (smtk::session::vtk::LegacyRead::*)(::smtk::session::vtk::LegacyRead const &)) &smtk::session::vtk::LegacyRead::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::vtk::LegacyRead::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::vtk::LegacyRead::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::vtk::LegacyRead::*)() const) &smtk::session::vtk::LegacyRead::shared_from_this) diff --git a/smtk/session/vtk/pybind11/PybindOperation.h b/smtk/session/vtk/pybind11/PybindOperation.h index 239ebda656..b929d0058d 100644 --- a/smtk/session/vtk/pybind11/PybindOperation.h +++ b/smtk/session/vtk/pybind11/PybindOperation.h @@ -22,9 +22,6 @@ namespace py = pybind11; inline PySharedPtrClass< smtk::session::vtk::Operation, smtk::operation::XMLOperation > pybind11_init_smtk_session_vtk_Operation(py::module &m) { PySharedPtrClass< smtk::session::vtk::Operation, smtk::operation::XMLOperation > instance(m, "Operation"); - instance - .def("deepcopy", (smtk::session::vtk::Operation & (smtk::session::vtk::Operation::*)(::smtk::session::vtk::Operation const &)) &smtk::session::vtk::Operation::operator=) - ; return instance; } diff --git a/smtk/session/vtk/pybind11/PybindRead.h b/smtk/session/vtk/pybind11/PybindRead.h index d4f77557f8..cf82ecd3b8 100644 --- a/smtk/session/vtk/pybind11/PybindRead.h +++ b/smtk/session/vtk/pybind11/PybindRead.h @@ -24,7 +24,6 @@ inline PySharedPtrClass< smtk::session::vtk::Read > pybind11_init_smtk_session_v PySharedPtrClass< smtk::session::vtk::Read > instance(m, "Read", parent); instance .def(py::init<>()) - .def("deepcopy", (smtk::session::vtk::Read & (smtk::session::vtk::Read::*)(::smtk::session::vtk::Read const &)) &smtk::session::vtk::Read::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::vtk::Read::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::vtk::Read::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::vtk::Read::*)() const) &smtk::session::vtk::Read::shared_from_this) diff --git a/smtk/session/vtk/pybind11/PybindWrite.h b/smtk/session/vtk/pybind11/PybindWrite.h index 88543d10ab..aeefca5ef5 100644 --- a/smtk/session/vtk/pybind11/PybindWrite.h +++ b/smtk/session/vtk/pybind11/PybindWrite.h @@ -24,7 +24,6 @@ inline PySharedPtrClass< smtk::session::vtk::Write > pybind11_init_smtk_session_ PySharedPtrClass< smtk::session::vtk::Write > instance(m, "Write", parent); instance .def(py::init<>()) - .def("deepcopy", (smtk::session::vtk::Write & (smtk::session::vtk::Write::*)(::smtk::session::vtk::Write const &)) &smtk::session::vtk::Write::operator=) .def_static("create", (std::shared_ptr (*)()) &smtk::session::vtk::Write::create) .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::session::vtk::Write::create, py::arg("ref")) .def("shared_from_this", (std::shared_ptr (smtk::session::vtk::Write::*)() const) &smtk::session::vtk::Write::shared_from_this) -- GitLab From 57ac40f250221f14ecbc27f103233eb6198daf95 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 10 Apr 2025 10:54:57 -0400 Subject: [PATCH 29/41] Make `smtk::task::Task::portData()` available in python. --- smtk/task/pybind11/PybindTask.h | 1 + 1 file changed, 1 insertion(+) diff --git a/smtk/task/pybind11/PybindTask.h b/smtk/task/pybind11/PybindTask.h index 58d9c0d8d1..5330f3cea1 100644 --- a/smtk/task/pybind11/PybindTask.h +++ b/smtk/task/pybind11/PybindTask.h @@ -35,6 +35,7 @@ inline PySharedPtrClass< smtk::task::Task, smtk::resource::Component > pybind11_ .def("title", &smtk::task::Task::name) .def("setTitle", &smtk::task::Task::setName, py::arg("title")) .def("ports", &smtk::task::Task::ports) + .def("portData", &smtk::task::Task::portData, py::arg("port")) .def("state", &smtk::task::Task::state) .def("agents", &smtk::task::Task::agents, py::return_value_policy::reference_internal) .def("children", &smtk::task::Task::children, py::return_value_policy::reference_internal) -- GitLab From 1c3debb3dd20e4894d0eb99e8b5aaa310d7a0357 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 23 Apr 2025 12:41:58 -0400 Subject: [PATCH 30/41] Add orientation-consistency to markup Import op. If importing polydata, you can force vtkOrientPolyData to run so that surface normals always point outward and are consistent. --- smtk/markup/operators/Import.cxx | 32 ++++++++++++++++++++++++++++++-- smtk/markup/operators/Import.h | 3 ++- smtk/markup/operators/Import.sbt | 3 +++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/smtk/markup/operators/Import.cxx b/smtk/markup/operators/Import.cxx index 14497caf18..b70d3e205d 100644 --- a/smtk/markup/operators/Import.cxx +++ b/smtk/markup/operators/Import.cxx @@ -54,6 +54,7 @@ #include "vtkLookupTable.h" #include "vtkMultiBlockDataSet.h" #include "vtkMultiThreshold.h" +#include "vtkOrientPolyData.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkSmartPointer.h" @@ -178,6 +179,20 @@ int maxDimension(vtkSmartPointer data) return result; } +vtkSmartPointer makeConsistent(const vtkSmartPointer& surface) +{ + vtkNew orient; + orient->SetInputDataObject(surface); + orient->ConsistencyOn(); + orient->AutoOrientNormalsOn(); + orient->NonManifoldTraversalOn(); + orient->Update(); + + auto result = vtkSmartPointer::New(); + result->ShallowCopy(orient->GetOutput()); + return result; +} + struct OwlNode { OwlNode() = default; @@ -371,6 +386,7 @@ Import::Result Import::operateInternal() } bool ok = true; + bool consistency = this->parameters()->findVoid("consistency")->isEnabled(); smtk::attribute::FileItem::Ptr filenameItem = this->parameters()->findFile("filename"); for (std::size_t ii = 0; ii < filenameItem->numberOfValues(); ++ii) { @@ -399,7 +415,7 @@ Import::Result Import::operateInternal() case ".vtp"_hash: // fall through case ".vtu"_hash: // fall through case ".vtk"_hash: - ok &= this->importVTKMesh(resource, filename); + ok &= this->importVTKMesh(resource, filename, consistency); break; default: { @@ -511,7 +527,10 @@ bool Import::importVTKImage(const Resource::Ptr& resource, const std::string& fi return true; } -bool Import::importVTKMesh(const Resource::Ptr& resource, const std::string& filename) +bool Import::importVTKMesh( + const Resource::Ptr& resource, + const std::string& filename, + bool consistency) { smtk::extension::vtk::io::ImportAsVTKData vtkImporter; auto data = vtkImporter(filename); @@ -519,6 +538,15 @@ bool Import::importVTKMesh(const Resource::Ptr& resource, const std::string& fil { return false; } + if (consistency) + { + auto* pp = vtkPolyData::SafeDownCast(data.GetPointer()); + if (pp) + { + vtkSmartPointer pdata(pp); + data = makeConsistent(pdata); + } + } auto createdItems = m_result->findComponent("created"); auto stem = smtk::common::Paths::stem(filename); diff --git a/smtk/markup/operators/Import.h b/smtk/markup/operators/Import.h index a30152b78a..8b48881974 100644 --- a/smtk/markup/operators/Import.h +++ b/smtk/markup/operators/Import.h @@ -59,7 +59,8 @@ protected: const std::string& filename); virtual bool importVTKMesh( const std::shared_ptr& resource, - const std::string& filename); + const std::string& filename, + bool consistency); virtual bool importOWL(const std::shared_ptr& resource, const std::string& filename); static std::map> supportedVTKFileFormats(); diff --git a/smtk/markup/operators/Import.sbt b/smtk/markup/operators/Import.sbt index 06855f1bbd..4cf7419d1a 100644 --- a/smtk/markup/operators/Import.sbt +++ b/smtk/markup/operators/Import.sbt @@ -20,6 +20,9 @@ FileFilters="VTK Unstructured Grids (*.vtu);; VTK Polydata (*.vtp);; VTK Image Data (*.vti);; Web Ontology Language (*.owl);;"> + + If enabled, force surfaces to have consistent, outward-pointing normals. +
    -- GitLab From 8b88285cc7014aace09c2cc4df62082a82816577 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 23 Apr 2025 14:07:13 -0400 Subject: [PATCH 31/41] Add convenience methods to ObjectsInRoles to fetch data. --- smtk/task/ObjectsInRoles.cxx | 46 +++++++++++++++++++++++ smtk/task/ObjectsInRoles.h | 36 ++++++++++++++++++ smtk/task/pybind11/PybindObjectsInRoles.h | 5 +++ 3 files changed, 87 insertions(+) diff --git a/smtk/task/ObjectsInRoles.cxx b/smtk/task/ObjectsInRoles.cxx index a59d276fc0..4e958e8143 100644 --- a/smtk/task/ObjectsInRoles.cxx +++ b/smtk/task/ObjectsInRoles.cxx @@ -9,6 +9,9 @@ //========================================================================= #include "smtk/task/ObjectsInRoles.h" +#include "smtk/task/Port.h" +#include "smtk/task/Task.h" + #include "smtk/resource/PersistentObject.h" namespace smtk @@ -83,5 +86,48 @@ bool ObjectsInRoles::merge(const PortData* other) return false; } +smtk::resource::PersistentObject* ObjectsInRoles::firstObjectInRoleOfType( + smtk::string::Token role, + smtk::string::Token objectType) +{ + auto it = m_data.find(role); + if (it == m_data.end()) + { + return nullptr; + } + + for (const auto& obj : it->second) + { + if (obj && obj->matchesType(objectType)) + { + return obj; + } + } + return nullptr; +} + +smtk::resource::PersistentObject* ObjectsInRoles::findTaskPortObjectInRoleOfType( + smtk::task::Task* task, + smtk::string::Token portName, + smtk::string::Token role, + smtk::string::Token objectType) +{ + if (!task || !portName.valid()) + { + return nullptr; + } + auto pit = task->ports().find(portName); + if (pit == task->ports().end()) + { + return nullptr; + } + auto portData = task->portData(pit->second); + if (auto objectsInRoles = std::dynamic_pointer_cast(portData)) + { + return objectsInRoles->firstObjectInRoleOfType(role, objectType); + } + return nullptr; +} + } // namespace task } // namespace smtk diff --git a/smtk/task/ObjectsInRoles.h b/smtk/task/ObjectsInRoles.h index 53ba42d5d0..4b4e159fa2 100644 --- a/smtk/task/ObjectsInRoles.h +++ b/smtk/task/ObjectsInRoles.h @@ -53,8 +53,44 @@ public: /// Reset all the data in the role map. bool clear(); + /// If \a other is also an instance of ObjectsInRoles, unite its data with \a this instance. bool merge(const PortData* other) override; + /// Find the first object of the given type in the given role. + smtk::resource::PersistentObject* firstObjectInRoleOfType( + smtk::string::Token role, + smtk::string::Token objectType); + + template + T* firstObjectInRoleAs(smtk::string::Token role, smtk::string::Token objectType) + { + T* value = dynamic_cast(this->firstObjectInRoleOfType(role, objectType)); + return value; + } + + /// This is a convenience for consumers of port data. + /// + /// It identifies a port on the task, fetches its port data, and then – assuming the + /// port data is an ObjectsInRoles instance – searches for an object in the given role + /// of the given type using firstObjectInRoleOfType(). + static smtk::resource::PersistentObject* findTaskPortObjectInRoleOfType( + smtk::task::Task* task, + smtk::string::Token portName, + smtk::string::Token role, + smtk::string::Token objectType); + + template + static T* findTaskPortObjectInRoleAs( + smtk::task::Task* task, + smtk::string::Token portName, + smtk::string::Token role, + smtk::string::Token objectType) + { + T* value = dynamic_cast( + ObjectsInRoles::findTaskPortObjectInRoleOfType(task, portName, role, objectType)); + return value; + } + protected: RoleMap m_data; }; diff --git a/smtk/task/pybind11/PybindObjectsInRoles.h b/smtk/task/pybind11/PybindObjectsInRoles.h index 11589470a4..8c8454a8f8 100644 --- a/smtk/task/pybind11/PybindObjectsInRoles.h +++ b/smtk/task/pybind11/PybindObjectsInRoles.h @@ -38,6 +38,11 @@ inline PySharedPtrClass< smtk::task::ObjectsInRoles > pybind11_init_smtk_task_Ob { return self.removeObject(object.get(), role); }, py::arg("object"), py::arg("role") = smtk::string::Token()) + .def("firstObjectInRoleOfType", [](smtk::task::ObjectsInRoles& oir, const std::string& role, const std::string& type) + { + auto* obj = oir.firstObjectInRoleOfType(role, type); + return obj; + }, py::arg("role"), py::arg("type")) ; return instance; } -- GitLab From b3e5ea452aea2137230e0be832ad7d884339f261 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 23 Apr 2025 14:08:29 -0400 Subject: [PATCH 32/41] =?UTF-8?q?Notify=20tasks=20of=20potentially-updated?= =?UTF-8?q?=20port=20data=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … when an operation marks a port as modified. Previously, this was only done for output ports. --- smtk/task/Manager.cxx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/smtk/task/Manager.cxx b/smtk/task/Manager.cxx index 9d23ca96bc..8e68989f47 100644 --- a/smtk/task/Manager.cxx +++ b/smtk/task/Manager.cxx @@ -135,14 +135,13 @@ nlohmann::json::array_t filtersForSpec(const smtk::view::Configuration::Componen std::string compMatch; filter.attribute("Component", compMatch); nlohmann::json::array_t fspec{ rsrcMatch }; - //NOLINTNEXTLINE(modernize-use-emplace) if (!compMatch.empty()) { - fspec.push_back(compMatch); + fspec.emplace_back(compMatch); } else { - fspec.push_back(std::nullptr_t()); + fspec.emplace_back(std::nullptr_t()); } filters.emplace_back(fspec); } @@ -713,6 +712,7 @@ void Manager::handleCreatedOrModifiedPort(const std::shared_ptrparent()->portDataUpdated(port.get()); + didUpdate = true; } } } + // Notify agents that the port data may have changed if they have not + // already been notified due to a direct (non-port) connection above. + if (!didUpdate) + { + port->parent()->portDataUpdated(port.get()); + } } } -- GitLab From 1919703e3e292772e3f94535aac00034da9f9c9a Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 24 Apr 2025 00:57:26 -0400 Subject: [PATCH 33/41] Notify ports immediately of TrivialProducer changes. --- smtk/task/TrivialProducerAgent.cxx | 7 +++++++ smtk/task/TrivialProducerAgent.h | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/smtk/task/TrivialProducerAgent.cxx b/smtk/task/TrivialProducerAgent.cxx index 821f1896eb..0e3ffbccf1 100644 --- a/smtk/task/TrivialProducerAgent.cxx +++ b/smtk/task/TrivialProducerAgent.cxx @@ -169,8 +169,10 @@ bool TrivialProducerAgent::addObjectInRole( bool didAdd = trivialProducer->m_data->addObject(object, role); if (didAdd) { + // Tell the task to update state based on this agent. trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); + trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); } return didAdd; } @@ -201,6 +203,7 @@ bool TrivialProducerAgent::addObjectInRole( { trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); + trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); } return didAdd; } @@ -231,6 +234,7 @@ bool TrivialProducerAgent::removeObjectFromRole( { trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); + trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); return true; } } @@ -261,6 +265,7 @@ bool TrivialProducerAgent::removeObjectFromRole( { trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); + trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); return true; } } @@ -288,6 +293,7 @@ bool TrivialProducerAgent::resetData(Task* task, const std::string& agentName) { trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); + trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); } } } @@ -312,6 +318,7 @@ bool TrivialProducerAgent::resetData(Task* task, Port* port) { trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); + trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); } } } diff --git a/smtk/task/TrivialProducerAgent.h b/smtk/task/TrivialProducerAgent.h index 776659a103..18da2d5ed9 100644 --- a/smtk/task/TrivialProducerAgent.h +++ b/smtk/task/TrivialProducerAgent.h @@ -69,6 +69,10 @@ public: /// /// Note that if multiple agents match the same \a agentName or \a port, /// only the first occurence will have \a object inserted. + /// + /// We notify downstream observers of the agent's output port with a call + /// to portDataUpdated(); be careful as this call to add objects is expected + /// to be called during an operation (which may run on a non-GUI thread). static bool addObjectInRole( Task* task, const std::string& agentName, @@ -84,6 +88,10 @@ public: /// /// Note that if multiple agents match the same \a agentName or \a port, /// only the first occurence will have \a object removed. + /// + /// We notify downstream observers of the agent's output port with a call + /// to portDataUpdated(); be careful as this call to add objects is expected + /// to be called during an operation (which may run on a non-GUI thread). static bool removeObjectFromRole( Task* task, const std::string& agentName, @@ -99,6 +107,10 @@ public: /// /// Note that if multiple agents match the same \a agentName or \a port, /// all of them will be reset. + /// + /// We notify downstream observers of the agent's output port with a call + /// to portDataUpdated(); be careful as this call to add objects is expected + /// to be called during an operation (which may run on a non-GUI thread). static bool resetData(Task* task, const std::string& agentName); static bool resetData(Task* task, Port* port); -- GitLab From 20a1bfc194a763a90963106fdfe0d0f7ae0cb169 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 28 Apr 2025 23:02:31 -0400 Subject: [PATCH 34/41] When writing markup geometry, ensure containing directory exists. --- smtk/markup/operators/Write.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smtk/markup/operators/Write.cxx b/smtk/markup/operators/Write.cxx index 2bdd7ffe2f..5928d83788 100644 --- a/smtk/markup/operators/Write.cxx +++ b/smtk/markup/operators/Write.cxx @@ -225,6 +225,9 @@ bool Write::writeData( return false; } + // Ensure directory holding filename exists: + smtk::common::Paths::createDirectory(smtk::common::Paths::directory(filename)); + if (const auto* udata = dynamic_cast(dataNode)) { // Could use mimeType to determine file format (e.g., XML or Legacy) -- GitLab From cfb836959c194980d580d700b58273f4ffb36f7e Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 28 Apr 2025 23:02:58 -0400 Subject: [PATCH 35/41] Python helpers to obtain active project/task from application context. --- smtk/pybind11/__init__.py.in | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/smtk/pybind11/__init__.py.in b/smtk/pybind11/__init__.py.in index 0e5e45bead..30b3e385ba 100644 --- a/smtk/pybind11/__init__.py.in +++ b/smtk/pybind11/__init__.py.in @@ -279,5 +279,32 @@ def importPythonOperation(module_name, operation_name): if opMgr: opMgr.registerOperation(module_name, operation_name); +def activeProject(context = None): + import smtk.common + import smtk.project + import smtk.task + if context is None: + context = self.applicationContext() + if context is None: + return None + projMgr = context.get('smtk.project.Manager') + if projMgr is None: + return None + for proj in projMgr.projectsSet(): + if proj is None: + continue + return proj + return None + +def activeTask(context = None): + import smtk.common + import smtk.project + import smtk.task + proj = activeProject(context) + if proj is None: + return None + task = proj.taskManager().active().task() + return task + def wrappingProtocol(): return 'pybind11' -- GitLab From 46b6696cd40f26e34344f40a9165c541f2eb1b33 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 28 Apr 2025 23:04:02 -0400 Subject: [PATCH 36/41] pqTaskControlView should not crash when there is no active task. --- smtk/extension/paraview/project/pqTaskControlView.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smtk/extension/paraview/project/pqTaskControlView.cxx b/smtk/extension/paraview/project/pqTaskControlView.cxx index b57184130f..bc04bc467b 100644 --- a/smtk/extension/paraview/project/pqTaskControlView.cxx +++ b/smtk/extension/paraview/project/pqTaskControlView.cxx @@ -696,6 +696,10 @@ void pqTaskControlView::updateWithActiveTask(smtk::task::Task* task) delete child; } } + if (!task) + { + return; + } // Now process all of this view's entries QLayout* activeLayout = nullptr; -- GitLab From 32c184cdffc2572e0f054abc35eab68dcca5b6ad Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 29 Apr 2025 14:54:26 -0400 Subject: [PATCH 37/41] Make tiled group views use a scroll area. This makes group views usable when vertical space is limited. --- smtk/extension/qt/qtGroupView.cxx | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/smtk/extension/qt/qtGroupView.cxx b/smtk/extension/qt/qtGroupView.cxx index 6f177e9d2a..c278606e8d 100644 --- a/smtk/extension/qt/qtGroupView.cxx +++ b/smtk/extension/qt/qtGroupView.cxx @@ -53,7 +53,7 @@ public: QList m_ChildViews, m_TabbedViews; QList m_PageWidgets; QList m_PageIcons; - QList m_Labels; + QList m_Labels; qtGroupViewInternals::Style m_style{ TABBED }; std::vector m_activeViews; int m_currentTabSelected{ 0 }; @@ -603,6 +603,26 @@ void qtGroupView::addTileEntry(qtBaseView* child) { return; } + // Find the scroll area for the view. If none exists, add it. + auto* area = frame->findChild(); + if (!area) + { + area = new QScrollArea(frame); + area->setObjectName("GroupViewScroll"); + area->setWidgetResizable(true); + auto* inner = new QWidget; + inner->setObjectName("GroupViewInner"); + auto* innerLayout = new QVBoxLayout; + innerLayout->setObjectName("GroupViewInnerLayout"); + innerLayout->setAlignment(Qt::AlignTop); + innerLayout->setMargin(0); + inner->setLayout(innerLayout); + area->setWidget(inner); + frame->layout()->addWidget(area); + area->show(); + inner->show(); + } + // Add the child to the scroll-area's inner widget. QObject::connect(child, &qtBaseView::modified, this, &qtGroupView::childModified); QLabel* label = new QLabel(child->configuration()->label().c_str(), this->Widget); m_internals->m_Labels.append(label); @@ -610,17 +630,13 @@ void qtGroupView::addTileEntry(qtBaseView* child) titleFont.setBold(true); titleFont.setItalic(true); label->setFont(titleFont); + area->widget()->layout()->addWidget(label); + area->widget()->layout()->addWidget(child->widget()); - QLayout* vLayout = this->Widget->layout(); - vLayout->setMargin(0); - vLayout->addWidget(label); - vLayout->addWidget(child->widget()); - vLayout->setAlignment(Qt::AlignTop); - if (child->isEmpty()) - { - label->hide(); - child->widget()->hide(); - } + // For debugging. + // std::cout + // << "Adding " << child->widget()->objectName().toStdString() + // << " to " << area->widget()->layout()->objectName().toStdString() << "\n"; } void qtGroupView::updateModelAssociation() -- GitLab From 9298775afa2af200bd410ea2538bcace3ac4f805 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 1 May 2025 06:27:20 -0400 Subject: [PATCH 38/41] =?UTF-8?q?Fix=20`qtOperationTypeModel::runOperation?= =?UTF-8?q?WithDefaults()`=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … so it will not signal users to edit parameters of an operation that is unable to operate unless the parameters are invalid. This way, operations that have valid parameters (or even no editable parameters) do not show up in the operation parameter-editor. --- smtk/extension/qt/qtOperationTypeModel.cxx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/smtk/extension/qt/qtOperationTypeModel.cxx b/smtk/extension/qt/qtOperationTypeModel.cxx index 657275e6b4..5e265a539e 100644 --- a/smtk/extension/qt/qtOperationTypeModel.cxx +++ b/smtk/extension/qt/qtOperationTypeModel.cxx @@ -481,7 +481,13 @@ void qtOperationTypeModel::runOperationWithDefaults( } else { - Q_EMIT this->editOperationParameters(typeIndex); + // Only offer to edit the parameters if they are not + // valid; operations may be unable to run for other + // reasons. + if (!operation->parameters()->isValid()) + { + Q_EMIT this->editOperationParameters(typeIndex); + } } } } -- GitLab From d9e18adbed58190d7404848673f8facedf9c22a7 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 6 May 2025 00:48:33 -0400 Subject: [PATCH 39/41] Fix TrivialProducerAgent; copy data rather than pass value. Because port-data objects may be merged, the trivial producer must copy its internal data when asked for port-data. Otherwise, the internal data may be modified by a merge operation. --- smtk/task/TrivialProducerAgent.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/smtk/task/TrivialProducerAgent.cxx b/smtk/task/TrivialProducerAgent.cxx index 0e3ffbccf1..495e0f6834 100644 --- a/smtk/task/TrivialProducerAgent.cxx +++ b/smtk/task/TrivialProducerAgent.cxx @@ -144,7 +144,9 @@ std::shared_ptr TrivialProducerAgent::portData(const Port* port) const { if (port == m_outputPort) { - return m_data; + auto result = std::make_shared(); + result->merge(m_data.get()); + return result; } return nullptr; } -- GitLab From bc1713d2b92761273c4a837afcc6d1663fe646d3 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 6 May 2025 00:53:42 -0400 Subject: [PATCH 40/41] Change TrivialProducerAgent method signatures. Methods that accept an agent name rather than a Port object return a pointer to any modified port; this allows callers to add the port to an operation result. --- smtk/task/TrivialProducerAgent.cxx | 27 +++++++++++++-------------- smtk/task/TrivialProducerAgent.h | 14 +++++++++----- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/smtk/task/TrivialProducerAgent.cxx b/smtk/task/TrivialProducerAgent.cxx index 495e0f6834..b4070f6af6 100644 --- a/smtk/task/TrivialProducerAgent.cxx +++ b/smtk/task/TrivialProducerAgent.cxx @@ -151,7 +151,7 @@ std::shared_ptr TrivialProducerAgent::portData(const Port* port) const return nullptr; } -bool TrivialProducerAgent::addObjectInRole( +Port* TrivialProducerAgent::addObjectInRole( Task* task, const std::string& agentName, smtk::string::Token role, @@ -159,7 +159,7 @@ bool TrivialProducerAgent::addObjectInRole( { if (!task || agentName.empty() || !object) { - return false; + return nullptr; } for (const auto& agent : task->agents()) { @@ -176,11 +176,11 @@ bool TrivialProducerAgent::addObjectInRole( trivialProducer, prev, trivialProducer->computeInternalState()); trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); } - return didAdd; + return trivialProducer->outputPort(); } } } - return false; + return nullptr; } bool TrivialProducerAgent::addObjectInRole( @@ -214,7 +214,7 @@ bool TrivialProducerAgent::addObjectInRole( return false; } -bool TrivialProducerAgent::removeObjectFromRole( +Port* TrivialProducerAgent::removeObjectFromRole( Task* task, const std::string& agentName, smtk::string::Token role, @@ -222,7 +222,7 @@ bool TrivialProducerAgent::removeObjectFromRole( { if (!task || agentName.empty() || !object) { - return false; + return nullptr; } // NOLINTNEXTLINE(readability-use-anyofallof) for (const auto& agent : task->agents()) @@ -237,12 +237,12 @@ bool TrivialProducerAgent::removeObjectFromRole( trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); - return true; + return trivialProducer->outputPort(); } } } } - return false; + return nullptr; } bool TrivialProducerAgent::removeObjectFromRole( @@ -276,12 +276,11 @@ bool TrivialProducerAgent::removeObjectFromRole( return false; } -bool TrivialProducerAgent::resetData(Task* task, const std::string& agentName) +Port* TrivialProducerAgent::resetData(Task* task, const std::string& agentName) { - bool didChange = false; if (!task || agentName.empty()) { - return didChange; + return nullptr; } for (const auto& agent : task->agents()) { @@ -290,17 +289,17 @@ bool TrivialProducerAgent::resetData(Task* task, const std::string& agentName) if (trivialProducer->name() == agentName) { State prev = trivialProducer->state(); - didChange |= trivialProducer->m_data->clear(); - if (didChange) + if (trivialProducer->m_data->clear()) { trivialProducer->parent()->updateAgentState( trivialProducer, prev, trivialProducer->computeInternalState()); trivialProducer->parent()->portDataUpdated(trivialProducer->outputPort()); + return trivialProducer->outputPort(); } } } } - return didChange; + return nullptr; } bool TrivialProducerAgent::resetData(Task* task, Port* port) diff --git a/smtk/task/TrivialProducerAgent.h b/smtk/task/TrivialProducerAgent.h index 18da2d5ed9..f5a976a3c6 100644 --- a/smtk/task/TrivialProducerAgent.h +++ b/smtk/task/TrivialProducerAgent.h @@ -73,7 +73,7 @@ public: /// We notify downstream observers of the agent's output port with a call /// to portDataUpdated(); be careful as this call to add objects is expected /// to be called during an operation (which may run on a non-GUI thread). - static bool addObjectInRole( + static Port* addObjectInRole( Task* task, const std::string& agentName, smtk::string::Token role, @@ -92,7 +92,7 @@ public: /// We notify downstream observers of the agent's output port with a call /// to portDataUpdated(); be careful as this call to add objects is expected /// to be called during an operation (which may run on a non-GUI thread). - static bool removeObjectFromRole( + static Port* removeObjectFromRole( Task* task, const std::string& agentName, smtk::string::Token role, @@ -105,13 +105,17 @@ public: ///\brief A helper to reset a TrivialProducerAgent owned by a task. /// - /// Note that if multiple agents match the same \a agentName or \a port, - /// all of them will be reset. + /// Note that if multiple agents match the same \a agentName, the + /// first of them will be reset. If multiple agents match + /// the same \a port, all of them will be reset. + /// This is done so that the variant which accepts an \a agentName + /// can return the modified Port. (Operations which modify ports must + /// add them to the operation result.) /// /// We notify downstream observers of the agent's output port with a call /// to portDataUpdated(); be careful as this call to add objects is expected /// to be called during an operation (which may run on a non-GUI thread). - static bool resetData(Task* task, const std::string& agentName); + static Port* resetData(Task* task, const std::string& agentName); static bool resetData(Task* task, Port* port); protected: -- GitLab From 4d2b41fb7b7093a01d18f15521011a3f13fd872b Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 6 May 2025 03:12:28 -0400 Subject: [PATCH 41/41] =?UTF-8?q?Make=20`SubmitOperationAgent::computeInte?= =?UTF-8?q?rnalState()`=20virtual=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … so that projects can inherit it for custom agents. The OpenFOAM wavetank does this. --- smtk/task/SubmitOperationAgent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smtk/task/SubmitOperationAgent.h b/smtk/task/SubmitOperationAgent.h index afd87e50f6..dd63a5b1c2 100644 --- a/smtk/task/SubmitOperationAgent.h +++ b/smtk/task/SubmitOperationAgent.h @@ -368,7 +368,7 @@ protected: const ObjectSet& objects); /// Check m_resourcesByRole to see if all requirements are met. - State computeInternalState(); + virtual State computeInternalState(); ParameterSpecMap m_parameterSpecs; ParameterWatchMap m_watching; -- GitLab