Workflows: Add UI for taskmaestro workflows

Discover taskmaestro workflows under ~/.taskmaestro/workflows/ and surface
them as a "Workflows" collection in the project tree alongside Scripts.
Each workflow's task input schema is auto-generated by introspecting the
Pydantic models via a Python helper (rips.taskmaestro_helper) and rendered
as PDM field bindings - scalars for str/int/float/bool, and PdmPtrField
pickers for fields typed as rips.Case / rips.WellPath / rips.View.

The Run button on a RimWorkflow writes input.yaml from current bindings
(rips object references serialized as __resinsight_ref__ maps), spawns
the helper's run subcommand, and streams output to a modeless log dialog.
The helper resolves __resinsight_ref__ markers back to live rips objects
via the running ResInsight's gRPC server before driving taskmaestro.
This commit is contained in:
Kristian Bendiksen authored and Magne Sjaastad committed 2026-10-05 19:56:36 +02:00
1 parent 63eaa1266d
commit 3a7b8f6be8
49 files changed
+2861

No files matched your search

+1
View File
@@ -128,6 +128,7 @@ list(
ProjectDataModel/ProcessControl/CMakeLists_files.cmake
ProjectDataModel/Polygons/CMakeLists_files.cmake
ProjectDataModel/Polygons/Cloud/CMakeLists_files.cmake
ProjectDataModel/Workflow/CMakeLists_files.cmake
ProjectDataModel/WellLog/CMakeLists_files.cmake
ProjectDataModel/WellLog/WellLogTrack/CMakeLists_files.cmake
ProjectDataModel/WellMeasurement/CMakeLists_files.cmake
@@ -60,6 +60,7 @@ set(COMMAND_REFERENCED_CMAKE_FILES
FractureCommands/CMakeLists_files.cmake
PlotBuilderCommands/CMakeLists_files.cmake
PolygonCommands/CMakeLists_files.cmake
WorkflowCommands/CMakeLists_files.cmake
Sumo/CMakeLists_files.cmake
ToolCommands/CMakeLists_files.cmake
3dView/CMakeLists_files.cmake
@@ -0,0 +1,14 @@
set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RicNewWorkflowJobFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicRunWorkflowJobFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicCancelWorkflowJobFeature.h
)
set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RicNewWorkflowJobFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicRunWorkflowJobFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCancelWorkflowJobFeature.cpp
)
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
list(APPEND COMMAND_CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
@@ -0,0 +1,46 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicCancelWorkflowJobFeature.h"
#include "Workflow/RimWorkflowJob.h"
#include "cafSelectionManagerTools.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicCancelWorkflowJobFeature, "RicCancelWorkflowJobFeature" );
void RicCancelWorkflowJobFeature::onActionTriggered( bool isChecked )
{
auto jobs = caf::selectedObjectsByType<RimWorkflowJob*>();
if ( jobs.size() != 1 ) return;
jobs.front()->cancelJob();
}
void RicCancelWorkflowJobFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "Cancel Job" );
actionToSetup->setIcon( QIcon( ":/stop.svg" ) );
}
bool RicCancelWorkflowJobFeature::isCommandEnabled() const
{
auto jobs = caf::selectedObjectsByType<RimWorkflowJob*>();
return jobs.size() == 1 && jobs.front()->isRunning();
}
@@ -0,0 +1,31 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
class RicCancelWorkflowJobFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
protected:
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
bool isCommandEnabled() const override;
};
@@ -0,0 +1,65 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicNewWorkflowJobFeature.h"
#include "Workflow/RimWorkflow.h"
#include "Workflow/RimWorkflowJob.h"
#include "RiaLogging.h"
#include "cafPdmDefaultObjectFactory.h"
#include "cafSelectionManagerTools.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicNewWorkflowJobFeature, "RicNewWorkflowJobFeature" );
void RicNewWorkflowJobFeature::onActionTriggered( bool isChecked )
{
auto workflows = caf::selectedObjectsByType<RimWorkflow*>();
if ( workflows.size() != 1 ) return;
RimWorkflow* workflow = workflows.front();
auto jobs = workflow->jobs();
if ( jobs.empty() )
{
RiaLogging::warning( "Cannot create a new job: workflow has no template job to clone." );
return;
}
auto* clone =
dynamic_cast<RimWorkflowJob*>( jobs.front()->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) );
if ( !clone ) return;
clone->setJobName( QString( "Job %1" ).arg( jobs.size() + 1 ) );
workflow->addJob( clone );
clone->resolveReferencesRecursively();
workflow->uiCapability()->updateAllRequiredEditors();
}
void RicNewWorkflowJobFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "New Job" );
actionToSetup->setIcon( QIcon( ":/caf/duplicate.svg" ) );
}
bool RicNewWorkflowJobFeature::isCommandEnabled() const
{
return caf::selectedObjectsByType<RimWorkflow*>().size() == 1;
}
@@ -0,0 +1,31 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
class RicNewWorkflowJobFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
protected:
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
bool isCommandEnabled() const override;
};
@@ -0,0 +1,46 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicRunWorkflowJobFeature.h"
#include "Workflow/RimWorkflowJob.h"
#include "cafSelectionManagerTools.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicRunWorkflowJobFeature, "RicRunWorkflowJobFeature" );
void RicRunWorkflowJobFeature::onActionTriggered( bool isChecked )
{
auto jobs = caf::selectedObjectsByType<RimWorkflowJob*>();
if ( jobs.size() != 1 ) return;
jobs.front()->runJob();
}
void RicRunWorkflowJobFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "Run" );
actionToSetup->setIcon( QIcon( ":/Play.svg" ) );
}
bool RicRunWorkflowJobFeature::isCommandEnabled() const
{
auto jobs = caf::selectedObjectsByType<RimWorkflowJob*>();
return jobs.size() == 1 && !jobs.front()->isRunning();
}
@@ -0,0 +1,31 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
class RicRunWorkflowJobFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
protected:
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
bool isCommandEnabled() const override;
};
@@ -102,6 +102,7 @@
#include "Tools/RimAutomationSettings.h"
#include "VerticalFlowPerformance/RimVfpDataCollection.h"
#include "VerticalFlowPerformance/RimVfpPlotCollection.h"
#include "Workflow/RimWorkflowCollection.h"
#include "RiuPlotMainWindow.h"
@@ -137,6 +138,9 @@ RimProject::RimProject()
CAF_PDM_InitFieldNoDefault( &scriptCollection, "ScriptCollection", "Octave Scripts", ":/octave.png" );
scriptCollection.xmlCapability()->disableIO();
CAF_PDM_InitFieldNoDefault( &workflowCollection, "WorkflowCollection", "Workflows", ":/Folder.png" );
workflowCollection.xmlCapability()->disableIO();
CAF_PDM_InitFieldNoDefault( &m_jobCollection, "JobCollection", "Jobs", ":/gear.png" );
CAF_PDM_InitFieldNoDefault( &m_mainPlotCollection, "MainPlotCollection", "Plots" );
@@ -196,6 +200,9 @@ RimProject::RimProject()
scriptCollection->uiCapability()->setUiName( "Scripts" );
scriptCollection->uiCapability()->setUiIconFromResourceString( ":/octave.png" );
workflowCollection = new RimWorkflowCollection();
workflowCollection->uiCapability()->setUiName( "Workflows" );
m_mainPlotCollection = new RimMainPlotCollection();
m_pinnedFieldCollection = new RimQuickAccessCollection();
m_jobCollection = new RimJobCollection();
@@ -1487,6 +1494,7 @@ void RimProject::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, Q
{
uiTreeOrdering.add( scriptCollection() );
uiTreeOrdering.add( jobCollection() );
uiTreeOrdering.add( workflowCollection() );
}
else if ( uiConfigName == "PlotWindow.Templates" )
{
@@ -52,6 +52,7 @@ class RimObservedSummaryData;
class RimOilField;
class RimColorLegendCollection;
class RimScriptCollection;
class RimWorkflowCollection;
class RimSummaryCase;
class RimSummaryEnsemble;
class RimSummaryCaseMainCollection;
@@ -98,6 +99,7 @@ public:
caf::PdmChildArrayField<RimOilField*> oilFields;
caf::PdmChildField<RimColorLegendCollection*> colorLegendCollection;
caf::PdmChildField<RimScriptCollection*> scriptCollection;
caf::PdmChildField<RimWorkflowCollection*> workflowCollection;
caf::PdmChildField<RimViewLinkerCollection*> viewLinkerCollection;
caf::PdmChildField<RimSummaryCalculationCollection*> calculationCollection;
caf::PdmChildField<RimGridCalculationCollection*> gridCalculationCollection;
@@ -0,0 +1,43 @@
set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimWorkflow.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowJob.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowTaskInput.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowFieldBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowStringBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowFloatBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowIntBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowBoolBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowCaseBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowWellPathBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowViewBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowDateBinding.h
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowFilePathBinding.h
)
set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimWorkflow.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowJob.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowTaskInput.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowFieldBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowStringBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowFloatBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowIntBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowBoolBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowCaseBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowWellPathBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowViewBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowDateBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowFilePathBinding.cpp
)
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
source_group(
"ProjectDataModel\\Workflow"
FILES ${SOURCE_GROUP_HEADER_FILES} ${SOURCE_GROUP_SOURCE_FILES}
${CMAKE_CURRENT_LIST_DIR}/CMakeLists_files.cmake
)
@@ -0,0 +1,204 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflow.h"
#include "RimWorkflowJob.h"
#include "RimWorkflowTaskInput.h"
#include "RiaLogging.h"
#include "RiaPreferences.h"
#include "cafCmdFeatureMenuBuilder.h"
#include <QDir>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QProcessEnvironment>
#include <QStandardPaths>
CAF_PDM_SOURCE_INIT( RimWorkflow, "Workflow" );
RimWorkflow::RimWorkflow()
{
CAF_PDM_InitObject( "Workflow", ":/Folder.png" );
CAF_PDM_InitFieldNoDefault( &m_name, "Name", "Name" );
m_name.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitFieldNoDefault( &m_description, "Description", "Description" );
m_description.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitFieldNoDefault( &m_workflowDirectory, "WorkflowDirectory", "Directory" );
m_workflowDirectory.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitFieldNoDefault( &m_loadError, "LoadError", "Load Error" );
m_loadError.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitFieldNoDefault( &m_jobs, "Jobs", "" );
}
QString RimWorkflow::name() const
{
return m_name();
}
void RimWorkflow::setWorkflowDirectory( const QString& directory )
{
m_workflowDirectory = directory;
QString name = QFileInfo( QDir( directory ).absolutePath() ).fileName();
m_name = name;
setUiName( name );
}
QString RimWorkflow::workflowDirectory() const
{
return m_workflowDirectory().path();
}
std::vector<RimWorkflowJob*> RimWorkflow::jobs() const
{
std::vector<RimWorkflowJob*> result;
result.reserve( m_jobs.size() );
for ( RimWorkflowJob* j : m_jobs.childrenByType() )
{
if ( j ) result.push_back( j );
}
return result;
}
void RimWorkflow::addJob( RimWorkflowJob* job )
{
if ( job ) m_jobs.push_back( job );
}
void RimWorkflow::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{
menuBuilder << "RicNewWorkflowJobFeature";
}
bool RimWorkflow::loadFromDirectory( QString* errorMessage )
{
m_jobs.deleteChildren();
m_loadError = "";
const QString dir = workflowDirectory();
if ( dir.isEmpty() ) return false;
QString python = RimWorkflow::findPythonExecutable();
if ( python.isEmpty() )
{
m_loadError = "No usable Python interpreter found";
RiaLogging::warning( QString( "Workflow '%1': %2" ).arg( dir, m_loadError() ).toStdString() );
if ( errorMessage ) *errorMessage = m_loadError;
return false;
}
QStringList args{ "-m", "rips.taskmaestro_helper", "introspect", dir };
QProcess proc;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
proc.setProcessEnvironment( env );
proc.start( python, args );
if ( !proc.waitForStarted( 10000 ) )
{
m_loadError = QString( "Could not launch '%1'" ).arg( python );
RiaLogging::warning( QString( "Workflow '%1': %2" ).arg( dir, m_loadError() ).toStdString() );
if ( errorMessage ) *errorMessage = m_loadError;
return false;
}
if ( !proc.waitForFinished( 30000 ) )
{
proc.kill();
m_loadError = "Introspect helper timed out";
RiaLogging::warning( QString( "Workflow '%1': %2" ).arg( dir, m_loadError() ).toStdString() );
if ( errorMessage ) *errorMessage = m_loadError;
return false;
}
const QByteArray stdoutBytes = proc.readAllStandardOutput();
if ( proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0 )
{
m_loadError = QString::fromUtf8( proc.readAllStandardError() ).trimmed();
if ( m_loadError().isEmpty() ) m_loadError = "Introspect helper failed";
RiaLogging::warning( QString( "Workflow '%1' introspect failed: %2" ).arg( dir, m_loadError() ).toStdString() );
if ( errorMessage ) *errorMessage = m_loadError;
return false;
}
QJsonParseError parseErr{};
const QJsonDocument doc = QJsonDocument::fromJson( stdoutBytes, &parseErr );
if ( parseErr.error != QJsonParseError::NoError || !doc.isObject() )
{
m_loadError = QString( "Invalid JSON from introspect: %1" ).arg( parseErr.errorString() );
if ( errorMessage ) *errorMessage = m_loadError;
return false;
}
const QJsonObject root = doc.object();
if ( root.contains( "name" ) ) m_name = root.value( "name" ).toString();
if ( root.contains( "description" ) ) m_description = root.value( "description" ).toString();
setUiName( m_name() );
std::vector<RimWorkflowTaskInput*> taskInputs;
for ( const QJsonValue& tv : root.value( "tasks" ).toArray() )
{
const QJsonObject taskObj = tv.toObject();
auto* input = new RimWorkflowTaskInput;
input->setTaskName( taskObj.value( "name" ).toString() );
input->buildFromSchema( taskObj.value( "config_fields" ).toArray() );
taskInputs.push_back( input );
}
auto* job = new RimWorkflowJob;
job->setJobName( "Default" );
job->setTaskInputs( taskInputs );
m_jobs.push_back( job );
RiaLogging::info(
QString( "Loaded workflow '%1' (%2 tasks with config) from %3" ).arg( m_name() ).arg( taskInputs.size() ).arg( dir ).toStdString() );
return true;
}
QString RimWorkflow::findPythonExecutable()
{
QStringList candidates;
if ( auto* prefs = RiaPreferences::current() )
{
QString configured = prefs->pythonExecutable();
if ( !configured.isEmpty() && configured != "python" ) candidates << configured;
}
candidates << "python3" << "python";
for ( const QString& cand : candidates )
{
if ( cand.contains( '/' ) || cand.contains( '\\' ) )
{
if ( QFileInfo( cand ).isExecutable() ) return cand;
}
else if ( !QStandardPaths::findExecutable( cand ).isEmpty() )
{
return cand;
}
}
return {};
}
@@ -0,0 +1,55 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafFilePath.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
class RimWorkflowJob;
class RimWorkflow : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflow();
QString name() const;
void setWorkflowDirectory( const QString& directory );
QString workflowDirectory() const;
bool loadFromDirectory( QString* errorMessage = nullptr );
std::vector<RimWorkflowJob*> jobs() const;
void addJob( RimWorkflowJob* job );
static QString findPythonExecutable();
protected:
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
private:
caf::PdmField<QString> m_name;
caf::PdmField<QString> m_description;
caf::PdmField<caf::FilePath> m_workflowDirectory;
caf::PdmField<QString> m_loadError;
caf::PdmChildArrayField<RimWorkflowJob*> m_jobs;
};
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowBoolBinding.h"
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowBoolBinding, "WorkflowBoolBinding" );
RimWorkflowBoolBinding::RimWorkflowBoolBinding()
{
CAF_PDM_InitField( &m_value, "Value", false, "Value" );
}
void RimWorkflowBoolBinding::applySchema( const QJsonObject& fieldSchema )
{
RimWorkflowFieldBinding::applySchema( fieldSchema );
if ( fieldSchema.contains( "default" ) )
{
m_value = fieldSchema.value( "default" ).toBool( false );
}
}
QString RimWorkflowBoolBinding::toYamlValue() const
{
return m_value() ? "true" : "false";
}
@@ -0,0 +1,37 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
class RimWorkflowBoolBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowBoolBinding();
void applySchema( const QJsonObject& fieldSchema ) override;
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_value; }
private:
caf::PdmField<bool> m_value;
};
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowCaseBinding.h"
#include "RimEclipseCase.h"
#include "RimTools.h"
CAF_PDM_SOURCE_INIT( RimWorkflowCaseBinding, "WorkflowCaseBinding" );
RimWorkflowCaseBinding::RimWorkflowCaseBinding()
{
CAF_PDM_InitFieldNoDefault( &m_case, "Case", "Case" );
}
QString RimWorkflowCaseBinding::toYamlValue() const
{
if ( m_case() == nullptr ) return "null";
return QString( "{__resinsight_ref__: EclipseCase, case_id: %1}" ).arg( m_case()->caseId() );
}
QList<caf::PdmOptionItemInfo> RimWorkflowCaseBinding::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_case ) RimTools::eclipseCaseOptionItems( &options );
return options;
}
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
#include "cafPdmPtrField.h"
class RimEclipseCase;
class RimWorkflowCaseBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowCaseBinding();
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_case; }
private:
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
caf::PdmPtrField<RimEclipseCase*> m_case;
};
@@ -0,0 +1,58 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowCollection.h"
#include <QDir>
#include <QFileInfo>
CAF_PDM_SOURCE_INIT( RimWorkflowCollection, "WorkflowCollection" );
RimWorkflowCollection::RimWorkflowCollection()
{
CAF_PDM_InitObject( "Workflows", ":/Folder.png" );
CAF_PDM_InitFieldNoDefault( &m_items, "Workflows", "" );
rescanWorkflows();
}
RimWorkflowCollection::~RimWorkflowCollection() = default;
QString RimWorkflowCollection::discoveryDirectory()
{
return QDir::homePath() + "/.taskmaestro/workflows";
}
void RimWorkflowCollection::rescanWorkflows()
{
deleteAllItems();
QDir dir( discoveryDirectory() );
if ( !dir.exists() ) return;
const QFileInfoList entries = dir.entryInfoList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
for ( const QFileInfo& entry : entries )
{
if ( !QFileInfo( entry.absoluteFilePath() + "/workflow.yaml" ).isFile() ) continue;
auto* workflow = new RimWorkflow;
workflow->setWorkflowDirectory( entry.absoluteFilePath() );
workflow->loadFromDirectory();
addItem( workflow );
}
}
@@ -0,0 +1,38 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflow.h"
#include "cafPdmObjectCollection.h"
#include <QString>
class RimWorkflowCollection : public caf::PdmObjectCollection<RimWorkflow>
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowCollection();
~RimWorkflowCollection() override;
void rescanWorkflows();
static QString discoveryDirectory();
};
@@ -0,0 +1,43 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowDateBinding.h"
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowDateBinding, "WorkflowDateBinding" );
RimWorkflowDateBinding::RimWorkflowDateBinding()
{
CAF_PDM_InitField( &m_value, "Value", QDate::currentDate(), "Value" );
}
void RimWorkflowDateBinding::applySchema( const QJsonObject& fieldSchema )
{
RimWorkflowFieldBinding::applySchema( fieldSchema );
if ( fieldSchema.contains( "default" ) )
{
QDate parsed = QDate::fromString( fieldSchema.value( "default" ).toString(), Qt::ISODate );
if ( parsed.isValid() ) m_value = parsed;
}
}
QString RimWorkflowDateBinding::toYamlValue() const
{
return QString( "\"%1\"" ).arg( m_value().toString( Qt::ISODate ) );
}
@@ -0,0 +1,39 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
#include <QDate>
class RimWorkflowDateBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowDateBinding();
void applySchema( const QJsonObject& fieldSchema ) override;
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_value; }
private:
caf::PdmField<QDate> m_value;
};
@@ -0,0 +1,71 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowFieldBinding.h"
#include <QJsonObject>
CAF_PDM_ABSTRACT_SOURCE_INIT( RimWorkflowFieldBinding, "WorkflowFieldBinding" );
RimWorkflowFieldBinding::RimWorkflowFieldBinding()
{
CAF_PDM_InitObject( "Field", ":/Bullet.png" );
CAF_PDM_InitFieldNoDefault( &m_fieldName, "FieldName", "Field" );
m_fieldName.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitFieldNoDefault( &m_description, "Description", "Description" );
m_description.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitField( &m_required, "Required", false, "Required" );
m_required.uiCapability()->setUiReadOnly( true );
}
QString RimWorkflowFieldBinding::fieldName() const
{
return m_fieldName();
}
void RimWorkflowFieldBinding::setFieldName( const QString& name )
{
m_fieldName = name;
setUiName( name );
}
void RimWorkflowFieldBinding::setDescription( const QString& description )
{
m_description = description;
}
void RimWorkflowFieldBinding::setRequired( bool required )
{
m_required = required;
}
void RimWorkflowFieldBinding::applySchema( const QJsonObject& fieldSchema )
{
setFieldName( fieldSchema.value( "name" ).toString() );
setDescription( fieldSchema.value( "description" ).toString() );
setRequired( fieldSchema.value( "required" ).toBool( false ) );
if ( auto* vf = valueField() )
{
vf->uiCapability()->setUiName( m_fieldName() );
if ( !m_description().isEmpty() ) vf->uiCapability()->setUiToolTip( m_description() );
}
}
@@ -0,0 +1,47 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafPdmField.h"
#include "cafPdmObject.h"
class QJsonObject;
class RimWorkflowFieldBinding : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowFieldBinding();
QString fieldName() const;
void setFieldName( const QString& name );
void setDescription( const QString& description );
void setRequired( bool required );
virtual void applySchema( const QJsonObject& fieldSchema );
virtual QString toYamlValue() const = 0;
virtual caf::PdmFieldHandle* valueField() = 0;
protected:
caf::PdmField<QString> m_fieldName;
caf::PdmField<QString> m_description;
caf::PdmField<bool> m_required;
};
@@ -0,0 +1,64 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowFilePathBinding.h"
#include "cafPdmUiFilePathEditor.h"
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowFilePathBinding, "WorkflowFilePathBinding" );
RimWorkflowFilePathBinding::RimWorkflowFilePathBinding()
: m_selectDirectory( false )
{
CAF_PDM_InitFieldNoDefault( &m_value, "Value", "Value" );
m_value.uiCapability()->setUiEditorTypeName( caf::PdmUiFilePathEditor::uiEditorTypeName() );
}
void RimWorkflowFilePathBinding::applySchema( const QJsonObject& fieldSchema )
{
RimWorkflowFieldBinding::applySchema( fieldSchema );
const QString fmt = fieldSchema.value( "format" ).toString();
m_selectDirectory = ( fmt == "directory-path" );
if ( fieldSchema.contains( "default" ) )
{
m_value = fieldSchema.value( "default" ).toString();
}
}
QString RimWorkflowFilePathBinding::toYamlValue() const
{
QString p = m_value().path();
p.replace( "\\", "\\\\" );
p.replace( "\"", "\\\"" );
return QString( "\"%1\"" ).arg( p );
}
void RimWorkflowFilePathBinding::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_value )
{
if ( auto* attr = dynamic_cast<caf::PdmUiFilePathEditorAttribute*>( attribute ) )
{
attr->m_selectDirectory = m_selectDirectory;
}
}
}
@@ -0,0 +1,43 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
#include "cafFilePath.h"
class RimWorkflowFilePathBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowFilePathBinding();
void applySchema( const QJsonObject& fieldSchema ) override;
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_value; }
protected:
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
private:
caf::PdmField<caf::FilePath> m_value;
bool m_selectDirectory;
};
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowFloatBinding.h"
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowFloatBinding, "WorkflowFloatBinding" );
RimWorkflowFloatBinding::RimWorkflowFloatBinding()
{
CAF_PDM_InitField( &m_value, "Value", 0.0, "Value" );
}
void RimWorkflowFloatBinding::applySchema( const QJsonObject& fieldSchema )
{
RimWorkflowFieldBinding::applySchema( fieldSchema );
if ( fieldSchema.contains( "default" ) )
{
m_value = fieldSchema.value( "default" ).toDouble();
}
}
QString RimWorkflowFloatBinding::toYamlValue() const
{
return QString::number( m_value(), 'g', 17 );
}
@@ -0,0 +1,37 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
class RimWorkflowFloatBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowFloatBinding();
void applySchema( const QJsonObject& fieldSchema ) override;
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_value; }
private:
caf::PdmField<double> m_value;
};
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowIntBinding.h"
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowIntBinding, "WorkflowIntBinding" );
RimWorkflowIntBinding::RimWorkflowIntBinding()
{
CAF_PDM_InitField( &m_value, "Value", 0, "Value" );
}
void RimWorkflowIntBinding::applySchema( const QJsonObject& fieldSchema )
{
RimWorkflowFieldBinding::applySchema( fieldSchema );
if ( fieldSchema.contains( "default" ) )
{
m_value = fieldSchema.value( "default" ).toInt();
}
}
QString RimWorkflowIntBinding::toYamlValue() const
{
return QString::number( m_value() );
}
@@ -0,0 +1,37 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
class RimWorkflowIntBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowIntBinding();
void applySchema( const QJsonObject& fieldSchema ) override;
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_value; }
private:
caf::PdmField<int> m_value;
};
@@ -0,0 +1,195 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowJob.h"
#include "RimWorkflow.h"
#include "RiaApplication.h"
#include "RiaLogging.h"
#include "RiuMainWindow.h"
#include "RiuWorkflowJobRunner.h"
#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmUiGroup.h"
#include "cafPdmUiOrdering.h"
#include "cafPdmUiTreeOrdering.h"
#include <QDir>
#include <QFile>
#include <QProcessEnvironment>
#include <QUuid>
CAF_PDM_SOURCE_INIT( RimWorkflowJob, "WorkflowJob" );
RimWorkflowJob::RimWorkflowJob()
{
CAF_PDM_InitObject( "Job", ":/Bullet.png" );
CAF_PDM_InitFieldNoDefault( &m_name, "Name", "Name" );
CAF_PDM_InitFieldNoDefault( &m_taskInputs, "TaskInputs", "" );
}
void RimWorkflowJob::setJobName( const QString& name )
{
m_name = name;
setUiName( name );
}
void RimWorkflowJob::initAfterRead()
{
setUiName( m_name() );
}
void RimWorkflowJob::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_name );
for ( RimWorkflowTaskInput* task : m_taskInputs.childrenByType() )
{
if ( !task ) continue;
caf::PdmUiGroup* group = uiOrdering.addNewGroup( task->taskName() );
for ( RimWorkflowFieldBinding* b : task->items() )
{
if ( b && b->valueField() ) group->add( b->valueField() );
}
}
uiOrdering.skipRemainingFields( true );
}
void RimWorkflowJob::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName )
{
uiTreeOrdering.skipRemainingChildren( true );
}
std::vector<RimWorkflowTaskInput*> RimWorkflowJob::taskInputs() const
{
std::vector<RimWorkflowTaskInput*> result;
result.reserve( m_taskInputs.size() );
for ( RimWorkflowTaskInput* t : m_taskInputs.childrenByType() )
{
if ( t ) result.push_back( t );
}
return result;
}
void RimWorkflowJob::setTaskInputs( std::vector<RimWorkflowTaskInput*> inputs )
{
m_taskInputs.deleteChildren();
for ( RimWorkflowTaskInput* t : inputs )
{
if ( t ) m_taskInputs.push_back( t );
}
}
void RimWorkflowJob::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_name )
{
setUiName( m_name() );
uiCapability()->updateConnectedEditors();
}
}
void RimWorkflowJob::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{
menuBuilder << "RicRunWorkflowJobFeature";
menuBuilder << "RicCancelWorkflowJobFeature";
}
bool RimWorkflowJob::isRunning() const
{
return m_runner && m_runner->isRunning();
}
void RimWorkflowJob::cancelJob()
{
if ( m_runner ) m_runner->cancel();
}
QString RimWorkflowJob::writeInputYaml( const QString& path ) const
{
QString body;
for ( RimWorkflowTaskInput* t : m_taskInputs.childrenByType() )
{
if ( t ) body += t->toTaskYamlBlock();
}
QFile out( path );
if ( !out.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) return {};
out.write( body.toUtf8() );
out.close();
return path;
}
void RimWorkflowJob::runJob()
{
if ( isRunning() )
{
RiaLogging::warning( "Job is already running." );
return;
}
auto* workflow = firstAncestorOrThisOfType<RimWorkflow>();
if ( !workflow )
{
RiaLogging::warning( "Cannot run job: parent workflow not found." );
return;
}
const QString dir = workflow->workflowDirectory();
if ( dir.isEmpty() ) return;
auto port = RiaApplication::instance()->activeGrpcPortNumber();
if ( !port.has_value() )
{
RiaLogging::warning( "Cannot run workflow: gRPC server is not active. Enable it in Preferences." );
return;
}
QString python = RimWorkflow::findPythonExecutable();
if ( python.isEmpty() )
{
RiaLogging::warning( "Cannot run workflow: no Python interpreter found." );
return;
}
QDir tmp( QDir::tempPath() );
QString runDir = QString( "resinsight_workflow_%1" ).arg( QUuid::createUuid().toString( QUuid::WithoutBraces ) );
if ( !tmp.mkpath( runDir ) )
{
RiaLogging::warning( "Cannot create temp dir for workflow run." );
return;
}
QString inputPath = tmp.absoluteFilePath( runDir + "/input.yaml" );
if ( writeInputYaml( inputPath ).isEmpty() )
{
RiaLogging::warning( "Failed to write input.yaml" );
return;
}
QStringList args{ "-m", "rips.taskmaestro_helper", "run", dir, "--input", inputPath, "--grpc-port", QString::number( port.value() ) };
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
const QString label = workflow->uiName() + " / " + m_name();
m_runner = new RiuWorkflowJobRunner( label, RiuMainWindow::instance() );
m_runner->start( python, args, env );
}
@@ -0,0 +1,59 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowTaskInput.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include <QPointer>
class RiuWorkflowJobRunner;
class RimWorkflowJob : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowJob();
void setJobName( const QString& name );
std::vector<RimWorkflowTaskInput*> taskInputs() const;
void setTaskInputs( std::vector<RimWorkflowTaskInput*> inputs );
QString writeInputYaml( const QString& path ) const;
void runJob();
void cancelJob();
bool isRunning() const;
protected:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void initAfterRead() override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override;
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
private:
caf::PdmField<QString> m_name;
caf::PdmChildArrayField<RimWorkflowTaskInput*> m_taskInputs;
QPointer<RiuWorkflowJobRunner> m_runner;
};
@@ -0,0 +1,45 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowStringBinding.h"
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowStringBinding, "WorkflowStringBinding" );
RimWorkflowStringBinding::RimWorkflowStringBinding()
{
CAF_PDM_InitFieldNoDefault( &m_value, "Value", "Value" );
}
void RimWorkflowStringBinding::applySchema( const QJsonObject& fieldSchema )
{
RimWorkflowFieldBinding::applySchema( fieldSchema );
if ( fieldSchema.contains( "default" ) )
{
m_value = fieldSchema.value( "default" ).toString();
}
}
QString RimWorkflowStringBinding::toYamlValue() const
{
QString v = m_value();
v.replace( "\\", "\\\\" );
v.replace( "\"", "\\\"" );
return QString( "\"%1\"" ).arg( v );
}
@@ -0,0 +1,37 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
class RimWorkflowStringBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowStringBinding();
void applySchema( const QJsonObject& fieldSchema ) override;
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_value; }
private:
caf::PdmField<QString> m_value;
};
@@ -0,0 +1,119 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowTaskInput.h"
#include "RimWorkflowBoolBinding.h"
#include "RimWorkflowCaseBinding.h"
#include "RimWorkflowDateBinding.h"
#include "RimWorkflowFilePathBinding.h"
#include "RimWorkflowFloatBinding.h"
#include "RimWorkflowIntBinding.h"
#include "RimWorkflowStringBinding.h"
#include "RimWorkflowViewBinding.h"
#include "RimWorkflowWellPathBinding.h"
#include "cafPdmUiOrdering.h"
#include "cafPdmUiTreeOrdering.h"
#include <QJsonArray>
#include <QJsonObject>
CAF_PDM_SOURCE_INIT( RimWorkflowTaskInput, "WorkflowTaskInput" );
namespace
{
RimWorkflowFieldBinding* createBinding( const QJsonObject& schema )
{
const QString resinsightType = schema.value( "resinsight_type" ).toString();
if ( resinsightType == "EclipseCase" ) return new RimWorkflowCaseBinding;
if ( resinsightType == "WellPath" ) return new RimWorkflowWellPathBinding;
if ( resinsightType == "View" ) return new RimWorkflowViewBinding;
const QString type = schema.value( "type" ).toString( "string" );
const QString format = schema.value( "format" ).toString();
if ( type == "string" && format == "date" ) return new RimWorkflowDateBinding;
if ( type == "string" && ( format == "path" || format == "file-path" || format == "directory-path" ) )
return new RimWorkflowFilePathBinding;
if ( type == "boolean" ) return new RimWorkflowBoolBinding;
if ( type == "integer" ) return new RimWorkflowIntBinding;
if ( type == "number" ) return new RimWorkflowFloatBinding;
return new RimWorkflowStringBinding;
}
} // namespace
RimWorkflowTaskInput::RimWorkflowTaskInput()
{
CAF_PDM_InitObject( "Task", ":/Bullet.png" );
CAF_PDM_InitFieldNoDefault( &m_items, "Bindings", "" );
CAF_PDM_InitFieldNoDefault( &m_taskName, "TaskName", "Task" );
m_taskName.uiCapability()->setUiReadOnly( true );
}
QString RimWorkflowTaskInput::taskName() const
{
return m_taskName();
}
void RimWorkflowTaskInput::setTaskName( const QString& name )
{
m_taskName = name;
setUiName( name );
}
void RimWorkflowTaskInput::buildFromSchema( const QJsonArray& configFields )
{
deleteAllItems();
for ( const QJsonValue& v : configFields )
{
const QJsonObject schema = v.toObject();
auto* binding = createBinding( schema );
binding->applySchema( schema );
addItem( binding );
}
}
void RimWorkflowTaskInput::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
for ( RimWorkflowFieldBinding* b : items() )
{
if ( b && b->valueField() ) uiOrdering.add( b->valueField() );
}
uiOrdering.skipRemainingFields( true );
}
void RimWorkflowTaskInput::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName )
{
uiTreeOrdering.skipRemainingChildren( true );
}
QString RimWorkflowTaskInput::toTaskYamlBlock() const
{
QString out = m_taskName() + ":\n";
if ( count() == 0 )
{
out += " {}\n";
return out;
}
for ( const RimWorkflowFieldBinding* b : items() )
{
out += QString( " %1: %2\n" ).arg( b->fieldName(), b->toYamlValue() );
}
return out;
}
@@ -0,0 +1,47 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
#include "cafPdmObjectCollection.h"
class QJsonArray;
class RimWorkflowTaskInput : public caf::PdmObjectCollection<RimWorkflowFieldBinding>
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowTaskInput();
QString taskName() const;
void setTaskName( const QString& name );
void buildFromSchema( const QJsonArray& configFields );
QString toTaskYamlBlock() const;
protected:
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override;
private:
caf::PdmField<QString> m_taskName;
};
@@ -0,0 +1,58 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowViewBinding.h"
#include "RimEclipseCase.h"
#include "RimEclipseCaseTools.h"
#include "RimEclipseView.h"
#include "RimProject.h"
CAF_PDM_SOURCE_INIT( RimWorkflowViewBinding, "WorkflowViewBinding" );
RimWorkflowViewBinding::RimWorkflowViewBinding()
{
CAF_PDM_InitFieldNoDefault( &m_view, "View", "View" );
}
QString RimWorkflowViewBinding::toYamlValue() const
{
if ( m_view() == nullptr ) return "null";
return QString( "{__resinsight_ref__: View, view_id: %1}" ).arg( m_view()->id() );
}
QList<caf::PdmOptionItemInfo> RimWorkflowViewBinding::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions != &m_view ) return options;
auto* project = RimProject::current();
if ( project == nullptr ) return options;
for ( RimEclipseCase* eclipseCase : RimEclipseCaseTools::eclipseCases() )
{
if ( eclipseCase == nullptr ) continue;
for ( RimEclipseView* view : eclipseCase->reservoirViews() )
{
if ( view == nullptr ) continue;
QString label = QString( "%1 / %2" ).arg( eclipseCase->caseUserDescription(), view->name() );
options.push_back( caf::PdmOptionItemInfo( label, view ) );
}
}
return options;
}
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
#include "cafPdmPtrField.h"
class RimEclipseView;
class RimWorkflowViewBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowViewBinding();
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_view; }
private:
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
caf::PdmPtrField<RimEclipseView*> m_view;
};
@@ -0,0 +1,44 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimWorkflowWellPathBinding.h"
#include "RimTools.h"
#include "RimWellPath.h"
CAF_PDM_SOURCE_INIT( RimWorkflowWellPathBinding, "WorkflowWellPathBinding" );
RimWorkflowWellPathBinding::RimWorkflowWellPathBinding()
{
CAF_PDM_InitFieldNoDefault( &m_wellPath, "WellPath", "Well Path" );
}
QString RimWorkflowWellPathBinding::toYamlValue() const
{
if ( m_wellPath() == nullptr ) return "null";
QString name = m_wellPath()->name();
name.replace( "\"", "\\\"" );
return QString( "{__resinsight_ref__: WellPath, well_path_name: \"%1\"}" ).arg( name );
}
QList<caf::PdmOptionItemInfo> RimWorkflowWellPathBinding::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_wellPath ) RimTools::wellPathOptionItems( &options );
return options;
}
@@ -0,0 +1,42 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimWorkflowFieldBinding.h"
#include "cafPdmPtrField.h"
class RimWellPath;
class RimWorkflowWellPathBinding : public RimWorkflowFieldBinding
{
CAF_PDM_HEADER_INIT;
public:
RimWorkflowWellPathBinding();
QString toYamlValue() const override;
caf::PdmFieldHandle* valueField() override { return &m_wellPath; }
private:
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
caf::PdmPtrField<RimWellPath*> m_wellPath;
};
@@ -1,3 +1,124 @@
set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RiuCadNavigation.h
${CMAKE_CURRENT_LIST_DIR}/RiuCursors.h
${CMAKE_CURRENT_LIST_DIR}/RiuDragDrop.h
${CMAKE_CURRENT_LIST_DIR}/RiuFemResultTextBuilder.h
${CMAKE_CURRENT_LIST_DIR}/RiuGeoQuestNavigation.h
${CMAKE_CURRENT_LIST_DIR}/RiuInterfaceToViewWindow.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotCurveSymbol.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotCurveSymbolImageCreator.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtSymbol.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotCurve.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotCurve.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotCurveDefines.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotItem.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItem.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotMainWindow.h
${CMAKE_CURRENT_LIST_DIR}/RiuMainWindow.h
${CMAKE_CURRENT_LIST_DIR}/RiuMainWindowBase.h
${CMAKE_CURRENT_LIST_DIR}/RiuMdiArea.h
${CMAKE_CURRENT_LIST_DIR}/RiuMdiSubWindow.h
${CMAKE_CURRENT_LIST_DIR}/RiuProcessMonitor.h
${CMAKE_CURRENT_LIST_DIR}/RiuProjectPropertyView.h
${CMAKE_CURRENT_LIST_DIR}/RiuPropertyViewTabWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuPropertyViewWizard.h
${CMAKE_CURRENT_LIST_DIR}/RiuPvtPlotPanel.h
${CMAKE_CURRENT_LIST_DIR}/RiuPvtPlotUpdater.h
${CMAKE_CURRENT_LIST_DIR}/RiuPvtPlotWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotUpdater.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtLinearScaleEngine.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtScalePicker.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtCurvePointTracker.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotWheelZoomer.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotZoomer.h
${CMAKE_CURRENT_LIST_DIR}/RiuWidgetDragger.h
${CMAKE_CURRENT_LIST_DIR}/RiuRecentFileActionProvider.h
${CMAKE_CURRENT_LIST_DIR}/RiuRelativePermeabilityPlotPanel.h
${CMAKE_CURRENT_LIST_DIR}/RiuRelativePermeabilityPlotUpdater.h
${CMAKE_CURRENT_LIST_DIR}/RiuResultInfoPanel.h
${CMAKE_CURRENT_LIST_DIR}/RiuResultQwtPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuResultTextBuilder.h
${CMAKE_CURRENT_LIST_DIR}/RiuRmsNavigation.h
${CMAKE_CURRENT_LIST_DIR}/RiuSelectionChangedHandler.h
${CMAKE_CURRENT_LIST_DIR}/Riu3dSelectionManager.h
${CMAKE_CURRENT_LIST_DIR}/RiuSimpleHistogramWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuDockedQwtPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuGridCrossQwtPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryQwtPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuTextDialog.h
${CMAKE_CURRENT_LIST_DIR}/RiuTimeStepChangedHandler.h
${CMAKE_CURRENT_LIST_DIR}/RiuTofAccumulatedPhaseFractionsPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuToolTipMenu.h
${CMAKE_CURRENT_LIST_DIR}/RiuTreeViewEventFilter.h
${CMAKE_CURRENT_LIST_DIR}/RiuViewer.h
${CMAKE_CURRENT_LIST_DIR}/RiuViewerToViewInterface.h
${CMAKE_CURRENT_LIST_DIR}/RiuViewerCommands.h
${CMAKE_CURRENT_LIST_DIR}/RiuCellAndNncPickEventHandler.h
${CMAKE_CURRENT_LIST_DIR}/RiuPickItemInfo.h
${CMAKE_CURRENT_LIST_DIR}/RiuWellLogPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuWellLogTrack.h
${CMAKE_CURRENT_LIST_DIR}/RiuMultiPlotPage.h
${CMAKE_CURRENT_LIST_DIR}/RiuMultiPlotBook.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryMultiPlotPage.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryMultiPlotBook.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotAxis.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotLegend.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotAnnotationTool.h
${CMAKE_CURRENT_LIST_DIR}/RiuGeoMechXfTensorResultAccessor.h
${CMAKE_CURRENT_LIST_DIR}/RiuFemTimeHistoryResultAccessor.h
${CMAKE_CURRENT_LIST_DIR}/RiuEditPerforationCollectionWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuAdvancedSnapshotExportWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuWellAllocationPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuFlowCharacteristicsPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuNightchartsWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuMessagePanel.h
${CMAKE_CURRENT_LIST_DIR}/RiuMessageDialog.h
${CMAKE_CURRENT_LIST_DIR}/RiuWorkflowJobRunner.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotObjectPicker.h
${CMAKE_CURRENT_LIST_DIR}/RiuContextMenuLauncher.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryCurveDefinitionKeywords.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryVectorSelectionUi.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryVectorSelectionDialog.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryVectorSelectionWidgetCreator.h
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryQuantityNameInfoProvider.h
${CMAKE_CURRENT_LIST_DIR}/RiuExpressionContextMenuManager.h
${CMAKE_CURRENT_LIST_DIR}/RiuCalculationsContextMenuManager.h
${CMAKE_CURRENT_LIST_DIR}/RiuGridStatisticsHistogramWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuMohrsCirclePlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuPlotMainWindowTools.h
${CMAKE_CURRENT_LIST_DIR}/Riu3DMainWindowTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuDockWidgetTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtCurveSelectorFilter.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItemGroup.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuDepthQwtPlot.h
${CMAKE_CURRENT_LIST_DIR}/RiuWellPathComponentPlotItem.h
${CMAKE_CURRENT_LIST_DIR}/RiuMeasurementViewEventFilter.h
${CMAKE_CURRENT_LIST_DIR}/RiuDraggableOverlayFrame.h
${CMAKE_CURRENT_LIST_DIR}/RiuMdiMaximizeWindowGuard.h
${CMAKE_CURRENT_LIST_DIR}/RiuMainWindowTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuComparisonViewMover.h
${CMAKE_CURRENT_LIST_DIR}/RiuAbstractOverlayContentFrame.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtLegendOverlayContentFrame.h
${CMAKE_CURRENT_LIST_DIR}/RiuAbstractLegendFrame.h
${CMAKE_CURRENT_LIST_DIR}/RiuSeismicHistogramPanel.h
${CMAKE_CURRENT_LIST_DIR}/RiuCategoryLegendFrame.h
${CMAKE_CURRENT_LIST_DIR}/RiuScalarMapperLegendFrame.h
${CMAKE_CURRENT_LIST_DIR}/RiuFileDialogTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuGuiTheme.h
${CMAKE_CURRENT_LIST_DIR}/RiuQssSyntaxHighlighter.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtDateScaleWrapper.h
${CMAKE_CURRENT_LIST_DIR}/RiuMatrixPlotWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuMenuBarBuildTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotRectAnnotation.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotZoomerMultiAxes.h
${CMAKE_CURRENT_LIST_DIR}/RiuCellSelectionTool.h
)
set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiuCadNavigation.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuCursors.cpp
@@ -72,6 +193,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiuNightchartsWidget.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuMessagePanel.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuMessageDialog.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuWorkflowJobRunner.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuPlotObjectPicker.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuContextMenuLauncher.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuSummaryVectorSelectionUi.cpp
@@ -0,0 +1,104 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiuWorkflowJobRunner.h"
RiuWorkflowJobRunner::RiuWorkflowJobRunner( const QString& label, QObject* parent )
: QObject( parent )
, m_label( label )
{
connect( &m_process, &QProcess::readyReadStandardOutput, this, &RiuWorkflowJobRunner::onReadyReadStdout );
connect( &m_process, &QProcess::readyReadStandardError, this, &RiuWorkflowJobRunner::onReadyReadStderr );
connect( &m_process, QOverload<int, QProcess::ExitStatus>::of( &QProcess::finished ), this, &RiuWorkflowJobRunner::onProcessFinished );
}
void RiuWorkflowJobRunner::start( const QString& program, const QStringList& arguments, const QProcessEnvironment& env )
{
RiaLogging::info( QString( "Running %1" ).arg( m_label ).toStdString() );
RiaLogging::info( QString( "[%1] $ %2 %3" ).arg( m_label, program, arguments.join( ' ' ) ).toStdString() );
m_process.setProcessEnvironment( env );
m_process.start( program, arguments );
}
void RiuWorkflowJobRunner::cancel()
{
if ( m_process.state() == QProcess::NotRunning ) return;
m_process.terminate();
if ( !m_process.waitForFinished( 2000 ) ) m_process.kill();
}
bool RiuWorkflowJobRunner::isRunning() const
{
return m_process.state() != QProcess::NotRunning;
}
void RiuWorkflowJobRunner::onReadyReadStdout()
{
m_stdoutBuf.append( m_process.readAllStandardOutput() );
drainLines( m_stdoutBuf, RILogLevel::RI_LL_INFO );
}
void RiuWorkflowJobRunner::onReadyReadStderr()
{
m_stderrBuf.append( m_process.readAllStandardError() );
drainLines( m_stderrBuf, RILogLevel::RI_LL_WARNING );
}
void RiuWorkflowJobRunner::onProcessFinished( int exitCode, QProcess::ExitStatus status )
{
m_stdoutBuf.append( m_process.readAllStandardOutput() );
m_stderrBuf.append( m_process.readAllStandardError() );
if ( !m_stdoutBuf.endsWith( '\n' ) ) m_stdoutBuf.append( '\n' );
if ( !m_stderrBuf.endsWith( '\n' ) ) m_stderrBuf.append( '\n' );
drainLines( m_stdoutBuf, RILogLevel::RI_LL_INFO );
drainLines( m_stderrBuf, RILogLevel::RI_LL_WARNING );
if ( status == QProcess::NormalExit && exitCode == 0 )
{
RiaLogging::info( QString( "Finished %1 (exit 0)" ).arg( m_label ).toStdString() );
}
else if ( status == QProcess::NormalExit )
{
RiaLogging::error( QString( "Finished %1 (exit %2)" ).arg( m_label ).arg( exitCode ).toStdString() );
}
else
{
RiaLogging::error( QString( "%1 crashed" ).arg( m_label ).toStdString() );
}
deleteLater();
}
void RiuWorkflowJobRunner::drainLines( QByteArray& buffer, RILogLevel level )
{
int nl;
while ( ( nl = buffer.indexOf( '\n' ) ) >= 0 )
{
QString line = QString::fromUtf8( buffer.left( nl ) );
buffer.remove( 0, nl + 1 );
if ( line.endsWith( '\r' ) ) line.chop( 1 );
if ( line.isEmpty() ) continue;
const std::string msg = QString( "[%1] %2" ).arg( m_label, line ).toStdString();
if ( level == RILogLevel::RI_LL_WARNING )
RiaLogging::warning( msg );
else
RiaLogging::info( msg );
}
}
@@ -0,0 +1,51 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaLogging.h"
#include <QByteArray>
#include <QObject>
#include <QProcess>
#include <QString>
#include <QStringList>
class RiuWorkflowJobRunner : public QObject
{
Q_OBJECT
public:
RiuWorkflowJobRunner( const QString& label, QObject* parent = nullptr );
void start( const QString& program, const QStringList& arguments, const QProcessEnvironment& env );
void cancel();
bool isRunning() const;
private slots:
void onReadyReadStdout();
void onReadyReadStderr();
void onProcessFinished( int exitCode, QProcess::ExitStatus status );
private:
void drainLines( QByteArray& buffer, RILogLevel level );
QString m_label;
QProcess m_process;
QByteArray m_stdoutBuf;
QByteArray m_stderrBuf;
};